From 54a86d7af3940a6ad12b78e6143fdf8b955b6a4c Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Mon, 26 Aug 2024 23:10:04 +0200 Subject: [PATCH 1/6] feat: new tls_multi_node test for molecule with some adjustment to tags --- CHANGELOG.md | 8 +- .../etc/hashistack/consul_servers/.gitkeep | 0 .../etc/hashistack/globals.yml | 10 +- .../hashistack/haproxy_servers/globals.yml | 8 - .../etc/hashistack/vault_servers/.gitkeep | 0 molecule/tls_multi_node/converge.yml | 3 + .../tls_multi_node/etc/hashistack/globals.yml | 299 +++++++++++++++++ molecule/tls_multi_node/molecule.yml | 116 +++++++ molecule/tls_multi_node/prepare.yml | 9 + molecule/tls_multi_node/requirements.yml | 10 + molecule/tls_multi_node/verify.yml | 6 + playbooks/deploy.yml | 18 + playbooks/group_vars/all/nomad.yml | 8 +- playbooks/preflight.yml | 48 --- playbooks/tasks/nomad/nomad_control_plane.yml | 18 +- playbooks/tasks/nomad/nomad_deploy.yml | 2 + roles/consul/.docsible | 13 + roles/consul/README.md | 227 +++++++++++++ roles/hashistack/.docsible | 13 + roles/hashistack/README.md | 123 +++++++ roles/hashistack_ca/.docsible | 13 + roles/hashistack_ca/README.md | 313 ++++++++++++++++++ roles/nomad/.docsible | 13 + roles/nomad/README.md | 221 +++++++++++++ roles/vault/.docsible | 13 + roles/vault/README.md | 193 +++++++++++ 26 files changed, 1634 insertions(+), 71 deletions(-) delete mode 100644 molecule/no_tls_multi_node/etc/hashistack/consul_servers/.gitkeep delete mode 100644 molecule/no_tls_multi_node/etc/hashistack/haproxy_servers/globals.yml delete mode 100644 molecule/no_tls_multi_node/etc/hashistack/vault_servers/.gitkeep create mode 100644 molecule/tls_multi_node/converge.yml create mode 100644 molecule/tls_multi_node/etc/hashistack/globals.yml create mode 100644 molecule/tls_multi_node/molecule.yml create mode 100644 molecule/tls_multi_node/prepare.yml create mode 100644 molecule/tls_multi_node/requirements.yml create mode 100644 molecule/tls_multi_node/verify.yml create mode 100644 roles/consul/.docsible create mode 100644 roles/consul/README.md create mode 100644 roles/hashistack/.docsible create mode 100644 roles/hashistack/README.md create mode 100644 roles/hashistack_ca/.docsible create mode 100644 roles/hashistack_ca/README.md create mode 100644 roles/nomad/.docsible create mode 100644 roles/nomad/README.md create mode 100644 roles/vault/.docsible create mode 100644 roles/vault/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f473fa..a0c0992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,7 +42,7 @@ - do not use run_once instructions as it is wildly unreliable - typo in credentials template preventing from generating the initial credential file -## 0.4.0 (2024-07-10) +## v0.4.0 (2024-07-10) ### Feat @@ -57,14 +57,14 @@ - implement longer wait to stabilize consul cluster before bootstrapping to avoid timeout errors -## 0.3.0 (2024-05-13) +## v0.3.0 (2024-05-13) ### Feat - **generate_credentials**: generate new accesor ids and vault token credentials - **vault**: enable consul service registration automatically if consul is also enabled -## 0.2.0 (2024-05-05) +## v0.2.0 (2024-05-05) ### Feat @@ -75,7 +75,7 @@ - **globals**: restore default globals.yml file, move changes to test directory - **vault/consul**: ensure idempotence of extra_volumes list to avoid restarting on each run due to slightly different service files -## 0.1.0 (2024-05-03) +## v0.1.0 (2024-05-03) ### Feat diff --git a/molecule/no_tls_multi_node/etc/hashistack/consul_servers/.gitkeep b/molecule/no_tls_multi_node/etc/hashistack/consul_servers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/molecule/no_tls_multi_node/etc/hashistack/globals.yml b/molecule/no_tls_multi_node/etc/hashistack/globals.yml index 66f4777..4a02ac8 100644 --- a/molecule/no_tls_multi_node/etc/hashistack/globals.yml +++ b/molecule/no_tls_multi_node/etc/hashistack/globals.yml @@ -1,12 +1,6 @@ --- # 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 ######## ########################## @@ -37,14 +31,14 @@ api_interface: "eth1" # external tls options # ######################## -enable_tls_external: true +# enable_tls_external: false # external_tls_externally_managed_certs: false ######################## # internal tls options # ######################## -enable_tls_internal: true +# enable_tls_internal: false # internal_tls_externally_managed_certs: false ##################################################### 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 deleted file mode 100644 index 5d6ecdc..0000000 --- a/molecule/no_tls_multi_node/etc/hashistack/haproxy_servers/globals.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -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/.gitkeep b/molecule/no_tls_multi_node/etc/hashistack/vault_servers/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/molecule/tls_multi_node/converge.yml b/molecule/tls_multi_node/converge.yml new file mode 100644 index 0000000..daad78c --- /dev/null +++ b/molecule/tls_multi_node/converge.yml @@ -0,0 +1,3 @@ +--- +- name: Include a playbook from a collection + ansible.builtin.import_playbook: ednz_cloud.hashistack.deploy.yml diff --git a/molecule/tls_multi_node/etc/hashistack/globals.yml b/molecule/tls_multi_node/etc/hashistack/globals.yml new file mode 100644 index 0000000..b0bca38 --- /dev/null +++ b/molecule/tls_multi_node/etc/hashistack/globals.yml @@ -0,0 +1,299 @@ +--- +# 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 ######## +########################## + +# enable_haproxy: "yes" +# enable_vault: "yes" +# enable_consul: "yes" +# enable_nomad: "yes" + +# haproxy_version: "2.8" +nomad_version: "1.8.3" +# consul_version: "1.18.1" +vault_version: "1.17.2" + +# 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 }}" + +api_interface: "eth1" +# api_interface_address: "{{ ansible_facts[api_interface]['ipv4']['address'] }}" + +######################## +# external tls options # +######################## + +enable_tls_external: true +# external_tls_externally_managed_certs: false + +######################## +# internal tls options # +######################## + +enable_tls_internal: true +# 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_gossip_encryption_key: "{{ 'mysupersecretgossipencryptionkey'|b64encode }}" + +################################ +# 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: "{{ enable_tls_internal }}" +# 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: [] + +##################################################### +# # +# Vault # +# # +##################################################### + +# vault_cluster_name: vault +# vault_enable_ui: true +# vault_seal_configuration: +# key_shares: 3 +# key_threshold: 2 + +################# +# vault storage # +################# + +# vault_storage_configuration: +# raft: +# path: "{{ hashicorp_vault_data_dir }}/data" +# 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: "{{ enable_tls_internal }}" +# 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_service_registration: false +# vault_service_registration_configuration: +# consul: +# address: "127.0.0.1:8500" +# scheme: "http" +# token: "" + +################# +# vault plugins # +################# + +# vault_enable_plugins: false + +########### +# logging # +########### + +# vault_enable_log_to_file: false +# vault_logging_configuration: +# log_level: info +# log_format: standard +# log_rotate_duration: 24h +# log_rotate_max_files: 30 + +########################### +# 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 ACL configuration # +########################### + +# nomad_acl_configuration: +# enabled: true +# token_ttl: 30s +# policy_ttl: 60s +# role_ttl: 60s + +############################ +# 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_extra_configuration: {} + +###################### +# nomad internal tls # +###################### + +# 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: "{{ hashistack_sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}" +# dest: "{{ nomad_certificates_directory }}" + +############################# +# nomad extra configuration # +############################# + +# nomad_extra_configuration: {} +# nomad_extra_files_list: [] diff --git a/molecule/tls_multi_node/molecule.yml b/molecule/tls_multi_node/molecule.yml new file mode 100644 index 0000000..39209e9 --- /dev/null +++ b/molecule/tls_multi_node/molecule.yml @@ -0,0 +1,116 @@ +--- +dependency: + name: galaxy + options: + requirements-file: ./requirements.yml +driver: + name: vagrant + provider: + name: libvirt +platforms: + - name: proxy01.ednz.lab + box: generic/${MOLECULE_TEST_OS} + cpus: 2 + memory: 2048 + interfaces: + - network_name: private_network + ip: 192.168.100.91 + auto_config: true + type: static + groups: + - common + - haproxy_servers + - nomad_clients + - consul_agents + - name: proxy02.ednz.lab + box: generic/${MOLECULE_TEST_OS} + cpus: 2 + memory: 2048 + interfaces: + - network_name: private_network + ip: 192.168.100.92 + auto_config: true + type: static + groups: + - common + - haproxy_servers + - nomad_clients + - consul_agents + - name: hashistack01.ednz.lab + box: generic/${MOLECULE_TEST_OS} + cpus: 4 + memory: 2048 + interfaces: + - network_name: private_network + ip: 192.168.100.101 + auto_config: true + type: static + groups: + - common + - vault_servers + - consul_servers + - nomad_servers + - nomad_clients + - name: hashistack02.ednz.lab + box: generic/${MOLECULE_TEST_OS} + cpus: 4 + memory: 2048 + interfaces: + - network_name: private_network + ip: 192.168.100.102 + auto_config: true + type: static + groups: + - common + - vault_servers + - consul_servers + - nomad_servers + - nomad_clients + - name: hashistack03.ednz.lab + box: generic/${MOLECULE_TEST_OS} + cpus: 4 + memory: 2048 + interfaces: + - network_name: private_network + ip: 192.168.100.103 + auto_config: true + type: static + groups: + - common + - vault_servers + - consul_servers + - nomad_servers + - nomad_clients + - name: hashistack04.ednz.lab + box: generic/${MOLECULE_TEST_OS} + cpus: 4 + memory: 2048 + interfaces: + - network_name: private_network + ip: 192.168.100.104 + auto_config: true + type: static + groups: + - common + - nomad_clients + - consul_agents +provisioner: + name: ansible + config_options: + defaults: + remote_tmp: /tmp/.ansible +verifier: + name: ansible +scenario: + name: tls_multi_node + test_sequence: + - dependency + - cleanup + - destroy + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/tls_multi_node/prepare.yml b/molecule/tls_multi_node/prepare.yml new file mode 100644 index 0000000..9973f22 --- /dev/null +++ b/molecule/tls_multi_node/prepare.yml @@ -0,0 +1,9 @@ +--- +- name: Include certificate generation playbook + ansible.builtin.import_playbook: ednz_cloud.hashistack.generate_certs.yml + +- name: Include bootstrap playbook + ansible.builtin.import_playbook: ednz_cloud.hashistack.bootstrap.yml + +- name: Include preflight playbook + ansible.builtin.import_playbook: ednz_cloud.hashistack.preflight.yml diff --git a/molecule/tls_multi_node/requirements.yml b/molecule/tls_multi_node/requirements.yml new file mode 100644 index 0000000..3dd371e --- /dev/null +++ b/molecule/tls_multi_node/requirements.yml @@ -0,0 +1,10 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_repositories + - name: ednz_cloud.manage_apt_packages + - name: ednz_cloud.manage_pip_packages + - name: ednz_cloud.install_docker + +collections: + - name: ednz_cloud.hashistack diff --git a/molecule/tls_multi_node/verify.yml b/molecule/tls_multi_node/verify.yml new file mode 100644 index 0000000..5f1bb76 --- /dev/null +++ b/molecule/tls_multi_node/verify.yml @@ -0,0 +1,6 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: [] diff --git a/playbooks/deploy.yml b/playbooks/deploy.yml index 4e2f613..356525d 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -10,6 +10,9 @@ - name: "Import variables" ansible.builtin.include_role: name: ednz_cloud.hashistack.hashistack + apply: + tags: + - always tags: - always @@ -17,28 +20,43 @@ - name: "Deploy Consul" tags: - consul + - consul_servers + - consul_agents when: - enable_consul | bool ansible.builtin.include_tasks: file: tasks/consul/consul_deploy.yml + apply: + tags: + - consul # Vault nodes deployment - name: "Deploy Vault" tags: - vault + - vault_servers when: - enable_vault | bool ansible.builtin.include_tasks: file: tasks/vault/vault_deploy.yml + apply: + tags: + - vault # Nomad nodes deployment - name: "Deploy Nomad" tags: - nomad + - nomad_servers + - nomad_clients when: - enable_nomad | bool ansible.builtin.include_tasks: file: tasks/nomad/nomad_deploy.yml + apply: + tags: + - nomad + # - fail: # Haproxy nodes deployment diff --git a/playbooks/group_vars/all/nomad.yml b/playbooks/group_vars/all/nomad.yml index 642228f..0203484 100644 --- a/playbooks/group_vars/all/nomad.yml +++ b/playbooks/group_vars/all/nomad.yml @@ -99,6 +99,12 @@ nomad_client_configuration: 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' if 'haproxy_servers' in group_names else + 'controller' if 'nomad_servers' in group_names else + omit + }} #################### # ui configuration # @@ -148,7 +154,7 @@ nomad_acl_configuration: # internal tls # ################ -nomad_enable_tls: false +# nomad_enable_tls: false nomad_tls_configuration: http: true rpc: true diff --git a/playbooks/preflight.yml b/playbooks/preflight.yml index 83d076c..92cab07 100644 --- a/playbooks/preflight.yml +++ b/playbooks/preflight.yml @@ -57,27 +57,6 @@ path: "{{ hashistack_configuration_directory }}" register: _stat_config_dir - - name: "Stat nomad_servers config directory" - ansible.builtin.stat: - 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: "{{ 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: "{{ hashistack_sub_configuration_directories.vault_servers }}" - register: _stat_config_dir_vault_servers - when: - - enable_vault | bool - - name: "Make sure directory exists: {{ hashistack_configuration_directory }}" ansible.builtin.assert: that: @@ -85,33 +64,6 @@ - _stat_config_dir.stat.isdir - _stat_config_dir.stat.writeable - - name: "Make sure directory exists: {{ hashistack_sub_configuration_directories.nomad_servers }}" - ansible.builtin.assert: - that: - - _stat_config_dir_nomad_servers.stat.exists - - _stat_config_dir_nomad_servers.stat.isdir - - _stat_config_dir_nomad_servers.stat.writeable - when: - - enable_nomad | bool - - - name: "Make sure directory exists: {{ hashistack_sub_configuration_directories.consul_servers }}" - ansible.builtin.assert: - that: - - _stat_config_dir_consul_servers.stat.exists - - _stat_config_dir_consul_servers.stat.isdir - - _stat_config_dir_consul_servers.stat.writeable - when: - - enable_consul | bool - - - name: "Make sure directory exists: {{ hashistack_sub_configuration_directories.vault_servers }}" - ansible.builtin.assert: - that: - - _stat_config_dir_vault_servers.stat.exists - - _stat_config_dir_vault_servers.stat.isdir - - _stat_config_dir_vault_servers.stat.writeable - when: - - 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 diff --git a/playbooks/tasks/nomad/nomad_control_plane.yml b/playbooks/tasks/nomad/nomad_control_plane.yml index da92dfd..87bd79b 100644 --- a/playbooks/tasks/nomad/nomad_control_plane.yml +++ b/playbooks/tasks/nomad/nomad_control_plane.yml @@ -67,15 +67,29 @@ state: present when: _consul_nomad_client_policy.changed - - name: "Include ednz_cloud.hashistack.cni" + - name: "Nomad | Include ednz_cloud.hashistack.cni" ansible.builtin.include_role: name: ednz_cloud.hashistack.cni when: nomad_enable_client - - name: "Include ednz_cloud.hashistack.nomad" + - name: "Nomad | Include ednz_cloud.hashistack.nomad" ansible.builtin.include_role: name: ednz_cloud.hashistack.nomad + - name: "Nomad | Wait for nomad cluster to initialize" + ansible.builtin.uri: + url: "{{ nomad_api_addr }}/v1/status/leader" + return_content: true + validate_certs: false + status_code: + - 200 + until: nomad_api_response.status == 200 + retries: 24 + delay: 5 + register: nomad_api_response + when: + - nomad_init_server + - name: "Nomad | Initialize nomad cluster" # noqa: run-once[task] ednz_cloud.hashistack.nomad_acl_bootstrap: bootstrap_secret: "{{ _credentials.nomad.root_token.secret_id }}" diff --git a/playbooks/tasks/nomad/nomad_deploy.yml b/playbooks/tasks/nomad/nomad_deploy.yml index 7bf78ba..543f3a8 100644 --- a/playbooks/tasks/nomad/nomad_deploy.yml +++ b/playbooks/tasks/nomad/nomad_deploy.yml @@ -7,6 +7,7 @@ when: - nomad_enable_server tags: + - nomad - nomad_servers - name: "Deploy Nomad Clients" @@ -16,4 +17,5 @@ - nomad_enable_client - not nomad_enable_server tags: + - nomad - nomad_clients diff --git a/roles/consul/.docsible b/roles/consul/.docsible new file mode 100644 index 0000000..7713e0f --- /dev/null +++ b/roles/consul/.docsible @@ -0,0 +1,13 @@ +aap_hub: null +automation_kind: null +category: null +critical: null +description: null +dt_dev: null +dt_prod: null +dt_update: 26/08/2024 +requester: null +subCategory: null +time_saving: null +users: null +version: null diff --git a/roles/consul/README.md b/roles/consul/README.md new file mode 100644 index 0000000..a644b6b --- /dev/null +++ b/roles/consul/README.md @@ -0,0 +1,227 @@ + + +# πŸ“ƒ Role overview + +## consul + + + +Description: Install and configure hashicorp consul for debian-based distros. + + +| Field | Value | +|--------------------- |-----------------| +| Readme update | 26/08/2024 | + + + + + + +### Defaults + +**These are static variables with lower priority** + +#### File: defaults/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [consul_version](defaults/main.yml#L4) | str | `latest` | n/a | n/a | +| [consul_start_service](defaults/main.yml#L5) | bool | `True` | n/a | n/a | +| [consul_config_dir](defaults/main.yml#L6) | str | `/etc/consul.d` | n/a | n/a | +| [consul_data_dir](defaults/main.yml#L7) | str | `/opt/consul` | n/a | n/a | +| [consul_certs_dir](defaults/main.yml#L8) | str | `{{ consul_config_dir }}/tls` | n/a | n/a | +| [consul_logs_dir](defaults/main.yml#L9) | str | `/var/log/consul` | n/a | n/a | +| [consul_envoy_install](defaults/main.yml#L11) | bool | `False` | n/a | n/a | +| [consul_envoy_version](defaults/main.yml#L12) | str | `latest` | n/a | n/a | +| [consul_extra_files](defaults/main.yml#L14) | bool | `False` | n/a | n/a | +| [consul_extra_files_list](defaults/main.yml#L15) | list | `[]` | n/a | n/a | +| [consul_env_variables](defaults/main.yml#L17) | dict | `{}` | n/a | n/a | +| [consul_extra_configuration](defaults/main.yml#L28) | dict | `{}` | n/a | n/a | +| [consul_domain](defaults/main.yml#L34) | str | `consul` | n/a | n/a | +| [consul_datacenter](defaults/main.yml#L35) | str | `dc1` | n/a | n/a | +| [consul_primary_datacenter](defaults/main.yml#L36) | str | `{{ consul_datacenter }}` | n/a | n/a | +| [consul_gossip_encryption_key](defaults/main.yml#L37) | str | `{{ 'mysupersecretgossipencryptionkey'\|b64encode }}` | n/a | n/a | +| [consul_enable_script_checks](defaults/main.yml#L38) | bool | `False` | n/a | n/a | +| [consul_leave_on_terminate](defaults/main.yml#L44) | bool | `True` | n/a | n/a | +| [consul_rejoin_after_leave](defaults/main.yml#L45) | bool | `True` | n/a | n/a | +| [consul_join_configuration](defaults/main.yml#L51) | dict | `{'retry_join': ['{{ ansible_default_ipv4.address }}'], 'retry_interval': '30s', 'retry_max': 0}` | n/a | n/a | +| [consul_enable_server](defaults/main.yml#L61) | bool | `True` | n/a | n/a | +| [consul_bootstrap_expect](defaults/main.yml#L62) | int | `1` | n/a | n/a | +| [consul_ui_configuration](defaults/main.yml#L68) | dict | `{'enabled': '{{ consul_enable_server }}'}` | n/a | n/a | +| [consul_bind_addr](defaults/main.yml#L75) | str | `0.0.0.0` | n/a | n/a | +| [consul_advertise_addr](defaults/main.yml#L76) | str | `{{ ansible_default_ipv4.address }}` | n/a | n/a | +| [consul_address_configuration](defaults/main.yml#L77) | dict | `{'client_addr': '{{ consul_bind_addr }}', 'bind_addr': '{{ consul_advertise_addr }}', 'advertise_addr': '{{ consul_advertise_addr }}'}` | n/a | n/a | +| [consul_acl_configuration](defaults/main.yml#L86) | dict | `{'enabled': False, 'default_policy': 'deny', 'enable_token_persistence': True}` | n/a | n/a | +| [consul_mesh_configuration](defaults/main.yml#L97) | dict | `{'enabled': False}` | n/a | n/a | +| [consul_dns_configuration](defaults/main.yml#L104) | dict | `{'allow_stale': True, 'enable_truncate': True, 'only_passing': True}` | n/a | n/a | +| [consul_enable_tls](defaults/main.yml#L113) | bool | `False` | n/a | n/a | +| [consul_tls_configuration](defaults/main.yml#L114) | dict | `{'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}}` | n/a | n/a | +| [consul_certificates_extra_files_dir](defaults/main.yml#L124) | list | `[]` | n/a | n/a | +| [consul_enable_prometheus_metrics](defaults/main.yml#L133) | bool | `False` | n/a | n/a | +| [consul_prometheus_retention_time](defaults/main.yml#L134) | str | `60s` | n/a | n/a | +| [consul_telemetry_configuration](defaults/main.yml#L135) | dict | `{}` | n/a | n/a | +| [consul_log_level](defaults/main.yml#L141) | str | `info` | n/a | n/a | +| [consul_enable_log_to_file](defaults/main.yml#L142) | bool | `False` | n/a | n/a | +| [consul_log_to_file_configuration](defaults/main.yml#L143) | dict | `{'log_file': '{{ consul_logs_dir }}/consul.log', 'log_rotate_duration': '24h', 'log_rotate_max_files': 30}` | n/a | n/a | + + +### Vars + +**These are variables with higher priority** +#### File: vars/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [consul_user](vars/main.yml#L3) | str | `consul` | n/a | n/a | +| [consul_group](vars/main.yml#L4) | str | `consul` | n/a | n/a | +| [consul_binary_path](vars/main.yml#L5) | str | `/usr/local/bin/consul` | n/a | n/a | +| [consul_envoy_binary_path](vars/main.yml#L6) | str | `/usr/local/bin/envoy` | n/a | n/a | +| [consul_deb_architecture_map](vars/main.yml#L7) | dict | `{'x86_64': 'amd64', 'aarch64': 'arm64', 'armv7l': 'arm', 'armv6l': 'arm'}` | n/a | n/a | +| [consul_envoy_architecture_map](vars/main.yml#L12) | dict | `{'x86_64': 'x86_64', 'aarch64': 'aarch64'}` | n/a | n/a | +| [consul_architecture](vars/main.yml#L15) | str | `{{ consul_deb_architecture_map[ansible_architecture] \| default(ansible_architecture) }}` | n/a | n/a | +| [consul_envoy_architecture](vars/main.yml#L16) | str | `{{ consul_envoy_architecture_map[ansible_architecture] \| default(ansible_architecture) }}` | n/a | n/a | +| [consul_service_name](vars/main.yml#L17) | str | `consul` | n/a | n/a | +| [consul_github_api](vars/main.yml#L18) | str | `https://api.github.com/repos` | n/a | n/a | +| [consul_envoy_github_project](vars/main.yml#L19) | str | `envoyproxy/envoy` | n/a | n/a | +| [consul_github_project](vars/main.yml#L20) | str | `hashicorp/consul` | n/a | n/a | +| [consul_github_url](vars/main.yml#L21) | str | `https://github.com` | n/a | n/a | +| [consul_repository_url](vars/main.yml#L22) | str | `https://releases.hashicorp.com/consul` | n/a | n/a | +| [consul_configuration](vars/main.yml#L24) | dict | `{'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}}` | n/a | n/a | +| [consul_configuration_string](vars/main.yml#L50) | str | `` | n/a | n/a | +| [consul_server_configuration_string](vars/main.yml#L57) | str | `` | n/a | n/a | + + +### Tasks + + +#### File: tasks/recursive_copy_extra_dirs.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Ensure destination directory exists | ansible.builtin.file | False | +| Consul \| Create extra directory sources | ansible.builtin.file | True | +| Consul \| Template extra directory sources | ansible.builtin.template | True | + +#### File: tasks/merge_variables.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Merge stringified configuration | vars | False | +| Consul \| Merge server specific stringified configuration | vars | True | +| Consul \| Merge join configuration | vars | False | +| Consul \| Merge addresses configuration | vars | False | +| Consul \| Merge TLS configuration | block | True | +| Consul \| Merge TLS configuration | vars | False | +| Consul \| Add certificates directory to extra_files_dir | ansible.builtin.set_fact | False | +| Consul \| Merge extra configuration settings | vars | False | +| Consul \| Merge log to file configuration | vars | True | +| Consul \| Merge telemetry configuration | block | False | +| Consul \| Merge prometheus metrics configuration | vars | True | +| Consul \| Merge telemtry configuration | vars | False | + +#### File: tasks/main.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Set reload-check & restart-check variable | ansible.builtin.set_fact | False | +| Consul \| Import merge_variables.yml | ansible.builtin.include_tasks | False | +| Consul \| Import prerequisites.yml | ansible.builtin.include_tasks | False | +| Consul \| Import install_envoy.yml | ansible.builtin.include_tasks | True | +| Consul \| Import install.yml | ansible.builtin.include_tasks | False | +| Consul \| Import configure.yml | ansible.builtin.include_tasks | False | +| Consul \| Populate service facts | ansible.builtin.service_facts | False | +| Consul \| Set restart-check variable | ansible.builtin.set_fact | True | +| Consul \| Enable service: {{ consul_service_name }} | ansible.builtin.service | False | +| Consul \| Reload systemd daemon | ansible.builtin.systemd | True | +| Consul \| Start service: {{ consul_service_name }} | ansible.builtin.service | True | + +#### File: tasks/install.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Get latest release of consul | block | True | +| Consul \| Get latest consul release from github api | ansible.builtin.uri | False | +| Consul \| Set wanted consul version to latest tag | ansible.builtin.set_fact | False | +| Consul \| Set wanted consul version to {{ consul_version }} | ansible.builtin.set_fact | True | +| Consul \| Get current consul version | block | False | +| Consul \| Stat consul version file | ansible.builtin.stat | False | +| Consul \| Get current consul version | ansible.builtin.slurp | True | +| Consul \| Download and install consul binary | block | True | +| Consul \| Set consul package name to download | ansible.builtin.set_fact | False | +| Consul \| Download checksum file for consul archive | ansible.builtin.get_url | False | +| Consul \| Extract correct checksum from checksum file | ansible.builtin.command | False | +| Consul \| Parse the expected checksum | ansible.builtin.set_fact | False | +| Consul \| Download consul binary archive | ansible.builtin.get_url | False | +| Consul \| Create temporary directory for archive decompression | ansible.builtin.file | False | +| Consul \| Unpack consul archive | ansible.builtin.unarchive | False | +| Consul \| Copy consul binary to {{ consul_binary_path }} | ansible.builtin.copy | False | +| Consul \| Update consul version file | ansible.builtin.copy | False | +| Consul \| Set restart-check variable | ansible.builtin.set_fact | False | +| Consul \| Cleanup temporary directory | ansible.builtin.file | False | +| Consul \| Copy systemd service file for consul | ansible.builtin.template | False | +| Consul \| Set reload-check & restart-check variable | ansible.builtin.set_fact | True | + +#### File: tasks/install_envoy.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Get release for envoy:{{ consul_envoy_version }} | vars | False | +| Consul \| Check if envoy is already installed | ansible.builtin.stat | False | +| Consul \| Check current envoy version | ansible.builtin.command | True | +| Consul \| Set facts for wanted envoy release | ansible.builtin.set_fact | True | +| Consul \| Set facts for current envoy release | ansible.builtin.set_fact | True | +| Consul \| Create envoy directory | ansible.builtin.file | False | +| Consul \| Install envoy | block | True | +| Consul \| Remove old compose binary if different | ansible.builtin.file | False | +| Consul \| Download and install envoy version:{{ consul_envoy_version }} | ansible.builtin.get_url | False | +| Consul \| Update version file | ansible.builtin.copy | False | + +#### File: tasks/prerequisites.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Create group {{ consul_group }} | ansible.builtin.group | False | +| Consul \| Create user {{ consul_user }} | ansible.builtin.user | False | +| Consul \| Create directory {{ consul_config_dir }} | ansible.builtin.file | False | +| Consul \| Create directory {{ consul_data_dir}} | ansible.builtin.file | False | +| Consul \| Create directory {{ consul_certs_dir }} | ansible.builtin.file | False | +| Consul \| Create directory {{ consul_logs_dir }} | ansible.builtin.file | True | + +#### File: tasks/configure.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul \| Create consul.env | ansible.builtin.template | False | +| Consul \| Copy consul.json template | ansible.builtin.template | False | +| Consul \| Set restart-check variable | ansible.builtin.set_fact | True | +| Consul \| Copy extra configuration files | block | True | +| Consul \| Get extra file types | ansible.builtin.stat | False | +| Consul \| Set list for file sources | vars | True | +| Consul \| Set list for directory sources | vars | True | +| Consul \| Template extra file sources | ansible.builtin.template | True | +| Consul \| Template extra directory sources | ansible.builtin.include_tasks | True | + + + + + + + +## Author Information +Bertrand Lanson + +#### License + +license (BSD, MIT) + +#### Minimum Ansible Version + +2.10 + +#### Platforms + +- **Ubuntu**: ['focal', 'jammy', 'noble'] +- **Debian**: ['bullseye', 'bookworm'] + + diff --git a/roles/hashistack/.docsible b/roles/hashistack/.docsible new file mode 100644 index 0000000..7713e0f --- /dev/null +++ b/roles/hashistack/.docsible @@ -0,0 +1,13 @@ +aap_hub: null +automation_kind: null +category: null +critical: null +description: null +dt_dev: null +dt_prod: null +dt_update: 26/08/2024 +requester: null +subCategory: null +time_saving: null +users: null +version: null diff --git a/roles/hashistack/README.md b/roles/hashistack/README.md new file mode 100644 index 0000000..2f6bb8b --- /dev/null +++ b/roles/hashistack/README.md @@ -0,0 +1,123 @@ + + +# πŸ“ƒ Role overview + +## hashistack + + + +Description: Merge variables for the playbooks contained in ednz_cloud.hashistack collection + + +| Field | Value | +|--------------------- |-----------------| +| Readme update | 26/08/2024 | + + + + + + +### Defaults + +**These are static variables with lower priority** + +#### File: defaults/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [hashistack_configuration_directory](defaults/main.yml#L3) | str | `{{ lookup('env', 'PWD') }}/etc/hashistack` | n/a | n/a | +| [hashistack_sub_configuration_directories](defaults/main.yml#L4) | dict | `{'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'}` | n/a | n/a | +| [hashistack_configuration_global_vars_file](defaults/main.yml#L11) | str | `globals.yml` | n/a | n/a | +| [hashistack_configuration_credentials_vars_file](defaults/main.yml#L12) | str | `credentials.yml` | n/a | n/a | +| [hashistack_remote_config_dir](defaults/main.yml#L14) | str | `/etc/hashistack` | n/a | n/a | +| [hashistack_remote_log_dir](defaults/main.yml#L15) | str | `/var/log/hashistack` | n/a | n/a | +| [hashistack_only_load_credentials](defaults/main.yml#L17) | bool | `False` | n/a | n/a | + + + + + +### Tasks + + +#### File: tasks/load_group_vars.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Variables \| Stat group specific config file | ansible.builtin.stat | False | +| Variables \| Load group specific variables | ansible.builtin.include_vars | True | + +#### File: tasks/load_credentials_vars.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Variables \| Stat credentials file | ansible.builtin.stat | False | +| Variables \| Stat vault credentials file | ansible.builtin.stat | False | +| Variables \| Make sure credentials file exists | ansible.builtin.assert | False | +| Variables \| Load credentials variables | ansible.builtin.include_vars | False | +| Variables \| Load vault credentials if vault.yml exists | ansible.builtin.include_vars | True | +| Variables \| Merge vault credentials into _credentials | vars | True | + +#### File: tasks/load_host_vars.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Variables \| Stat host specific config file | ansible.builtin.stat | False | +| Variables \| Load host specific variables | ansible.builtin.include_vars | True | + +#### File: tasks/main.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Variables \| Load global variables | ansible.builtin.include_tasks | True | +| Variables \| Load credentials variables | ansible.builtin.include_tasks | False | +| Variables \| Load group specific variables | ansible.builtin.include_tasks | True | +| Variables \| Load host specific variables | ansible.builtin.include_tasks | True | +| Ensure remote directories exists | ansible.builtin.file | True | +| Variables \| Load custom CA certificates | ansible.builtin.include_tasks | True | + +#### File: tasks/load_ca_certificates.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Check if CA directory exists | ansible.builtin.stat | False | +| Find custom ca certificates to copy | ansible.builtin.find | True | +| Ensure remote ca directory exists | ansible.builtin.file | False | +| Copy custom ca certificates | ansible.builtin.copy | True | +| Copy and update trust store | block | True | +| Copy ca certificates to /usr/local/share/ca-certificates | ansible.builtin.file | False | +| Update the trust store | ansible.builtin.command | True | + +#### File: tasks/load_global_vars.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Variables \| Include all default variables | ansible.builtin.include_vars | False | +| Variables \| Stat global configuration file | ansible.builtin.stat | False | +| Variables \| Make sure global configuration file exists | ansible.builtin.assert | False | +| Variables \| Load global variables | ansible.builtin.include_vars | False | + + + + + + + +## Author Information +Bertrand Lanson + +#### License + +license (BSD, MIT) + +#### Minimum Ansible Version + +2.10 + +#### Platforms + +- **Ubuntu**: ['focal', 'jammy', 'noble'] +- **Debian**: ['bullseye', 'bookworm'] + + diff --git a/roles/hashistack_ca/.docsible b/roles/hashistack_ca/.docsible new file mode 100644 index 0000000..7713e0f --- /dev/null +++ b/roles/hashistack_ca/.docsible @@ -0,0 +1,13 @@ +aap_hub: null +automation_kind: null +category: null +critical: null +description: null +dt_dev: null +dt_prod: null +dt_update: 26/08/2024 +requester: null +subCategory: null +time_saving: null +users: null +version: null diff --git a/roles/hashistack_ca/README.md b/roles/hashistack_ca/README.md new file mode 100644 index 0000000..08e3d09 --- /dev/null +++ b/roles/hashistack_ca/README.md @@ -0,0 +1,313 @@ + + +# πŸ“ƒ Role overview + +## hashistack_ca + + + + +Description: Not available. + +| Field | Value | +|--------------------- |-----------------| +| Readme update | 26/08/2024 | + + + + + + +### Defaults + +**These are static variables with lower priority** + +#### File: defaults/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [hashistack_ca_directory](defaults/main.yml#L3) | str | `/etc/hashistack/certificates` | n/a | n/a | +| [hashistack_ca_use_cryptography](defaults/main.yml#L4) | bool | `False` | n/a | n/a | +| [hashistack_ca_action](defaults/main.yml#L5) | str | `noop` | n/a | n/a | +| [hashistack_ca_domain](defaults/main.yml#L6) | str | `example.com` | n/a | n/a | +| [hashistack_ca_directory_owner](defaults/main.yml#L7) | str | `root` | n/a | n/a | +| [hashistack_ca_root_org_name](defaults/main.yml#L12) | str | `EDNZ Cloud` | n/a | n/a | +| [hashistack_ca_root_country](defaults/main.yml#L13) | str | `FR` | n/a | n/a | +| [hashistack_ca_root_locality](defaults/main.yml#L14) | str | `Paris` | n/a | n/a | +| [hashistack_ca_root_common_name](defaults/main.yml#L15) | str | `{{ hashistack_ca_domain }} Root CA` | n/a | n/a | +| [hashistack_ca_root_email](defaults/main.yml#L16) | NoneType | `None` | n/a | n/a | +| [hashistack_ca_root_key_usage](defaults/main.yml#L17) | list | `['keyCertSign', 'cRLSign']` | n/a | n/a | +| [hashistack_ca_root_key_usage_critical](defaults/main.yml#L20) | bool | `True` | n/a | n/a | +| [hashistack_ca_root_basic_constraints](defaults/main.yml#L21) | list | `['CA:TRUE']` | n/a | n/a | +| [hashistack_ca_root_basic_constraints_critical](defaults/main.yml#L23) | bool | `True` | n/a | n/a | +| [hashistack_ca_root_state_or_province_name](defaults/main.yml#L26) | NoneType | `None` | n/a | n/a | +| [hashistack_ca_root_email_address](defaults/main.yml#L27) | NoneType | `None` | n/a | n/a | +| [hashistack_ca_root_valid_for](defaults/main.yml#L30) | str | `1825d` | n/a | n/a | +| [hashistack_ca_root_renew_threshold](defaults/main.yml#L31) | str | `180d` | n/a | n/a | +| [hashistack_ca_intermediate_org_name](defaults/main.yml#L36) | str | `EDNZ Cloud Intermediate` | n/a | n/a | +| [hashistack_ca_intermediate_country](defaults/main.yml#L37) | str | `FR` | n/a | n/a | +| [hashistack_ca_intermediate_locality](defaults/main.yml#L38) | str | `Paris` | n/a | n/a | +| [hashistack_ca_intermediate_common_name](defaults/main.yml#L39) | str | `{{ hashistack_ca_domain }} Intermediate CA` | n/a | n/a | +| [hashistack_ca_intermediate_email](defaults/main.yml#L40) | NoneType | `None` | n/a | n/a | +| [hashistack_ca_intermediate_key_usage](defaults/main.yml#L41) | list | `['keyCertSign', 'cRLSign']` | n/a | n/a | +| [hashistack_ca_intermediate_key_usage_critical](defaults/main.yml#L44) | bool | `True` | n/a | n/a | +| [hashistack_ca_intermediate_basic_constraints](defaults/main.yml#L45) | list | `['CA:TRUE', 'pathlen:0']` | n/a | n/a | +| [hashistack_ca_intermediate_basic_constraints_critical](defaults/main.yml#L48) | bool | `True` | n/a | n/a | +| [hashistack_ca_intermediate_state_or_province_name](defaults/main.yml#L51) | NoneType | `None` | n/a | n/a | +| [hashistack_ca_intermediate_email_address](defaults/main.yml#L52) | NoneType | `None` | n/a | n/a | +| [hashistack_ca_intermediate_valid_for](defaults/main.yml#L55) | str | `365d` | n/a | n/a | +| [hashistack_ca_intermediate_renew_threshold](defaults/main.yml#L56) | str | `90d` | n/a | n/a | +| [hashistack_ca_intermediate_name_constraints_permitted](defaults/main.yml#L59) | list | `['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']` | n/a | n/a | +| [hashistack_ca_intermediate_name_constraints_critical](defaults/main.yml#L68) | str | `{{ (hashistack_ca_intermediate_name_constraints_permitted is defined and hashistack_ca_intermediate_name_constraints_permitted \| length > 0) }}` | n/a | n/a | +| [hashistack_ca_leaf_valid_for](defaults/main.yml#L74) | str | `90d` | n/a | n/a | +| [hashistack_ca_leaf_renew_threshold](defaults/main.yml#L75) | str | `30d` | n/a | n/a | +| [hashistack_ca_consul_org_name](defaults/main.yml#L80) | str | `{{ hashistack_ca_root_org_name }}` | n/a | n/a | +| [hashistack_ca_consul_common_name](defaults/main.yml#L81) | str | `{{ inventory_hostname }}` | n/a | n/a | +| [hashistack_ca_consul_csr_sans](defaults/main.yml#L82) | list | `['DNS:consul.service.consul', 'DNS:localhost', 'IP:127.0.0.1']` | n/a | n/a | +| [hashistack_ca_nomad_org_name](defaults/main.yml#L90) | str | `{{ hashistack_ca_root_org_name }}` | n/a | n/a | +| [hashistack_ca_nomad_common_name](defaults/main.yml#L91) | str | `{{ inventory_hostname }}` | n/a | n/a | +| [hashistack_ca_nomad_csr_sans](defaults/main.yml#L92) | list | `['DNS:server.global.nomad', 'DNS:client.global.nomad', 'DNS:nomad.service.consul', 'DNS:localhost', 'IP:127.0.0.1']` | n/a | n/a | +| [hashistack_ca_vault_org_name](defaults/main.yml#L102) | str | `{{ hashistack_ca_root_org_name }}` | n/a | n/a | +| [hashistack_ca_vault_common_name](defaults/main.yml#L103) | str | `{{ inventory_hostname }}` | n/a | n/a | +| [hashistack_ca_vault_csr_sans](defaults/main.yml#L104) | list | `['DNS:vault.service.consul', 'DNS:active.vault.service.consul', 'DNS:standby.vault.service.consul', 'DNS:localhost', 'IP:127.0.0.1']` | n/a | n/a | + + +### Vars + +**These are variables with higher priority** +#### File: vars/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [hashistack_ca_action_list](vars/main.yml#L3) | str | `{{ hashistack_ca_action.split(',') }}` | n/a | n/a | +| [hashistack_ca_generate_root](vars/main.yml#L6) | str | `{{ 'root_ca' in hashistack_ca_action_list }}` | n/a | n/a | +| [hashistack_ca_generate_intermediate](vars/main.yml#L7) | str | `{{ 'int_ca' in hashistack_ca_action_list }}` | n/a | n/a | +| [hashistack_ca_generate_leaf](vars/main.yml#L8) | str | `{{ 'leaf_cert' in hashistack_ca_action_list }}` | n/a | n/a | +| [hashistack_ca_renew_root](vars/main.yml#L9) | str | `{{ 'renew_root' in hashistack_ca_action_list }}` | n/a | n/a | +| [hashistack_ca_renew_intermediate](vars/main.yml#L10) | str | `{{ 'renew_int' in hashistack_ca_action_list }}` | n/a | n/a | +| [hashistack_ca_renew_leaf](vars/main.yml#L11) | str | `{{ 'renew_leaf' in hashistack_ca_action_list }}` | n/a | n/a | +| [hashistack_ca_public_dir](vars/main.yml#L13) | str | `{{ hashistack_ca_directory }}/ca` | n/a | n/a | +| [hashistack_ca_root_dir](vars/main.yml#L15) | str | `{{ hashistack_ca_directory }}/root` | n/a | n/a | +| [hashistack_ca_root_backup_dir](vars/main.yml#L16) | str | `{{ hashistack_ca_root_dir }}/backup` | n/a | n/a | +| [hashistack_ca_root_key_path](vars/main.yml#L17) | str | `{{ hashistack_ca_root_dir }}/ca.key` | n/a | n/a | +| [hashistack_ca_root_cert_path](vars/main.yml#L18) | str | `{{ hashistack_ca_root_dir }}/ca.crt` | n/a | n/a | +| [hashistack_ca_intermediate_dir](vars/main.yml#L20) | str | `{{ hashistack_ca_directory }}/intermediate` | n/a | n/a | +| [hashistack_ca_intermediate_backup_dir](vars/main.yml#L21) | str | `{{ hashistack_ca_intermediate_dir }}/backup` | n/a | n/a | +| [hashistack_ca_intermediate_key_path](vars/main.yml#L22) | str | `{{ hashistack_ca_intermediate_dir }}/ca.key` | n/a | n/a | +| [hashistack_ca_intermediate_csr_path](vars/main.yml#L23) | str | `{{ hashistack_ca_intermediate_dir }}/ca.csr` | n/a | n/a | +| [hashistack_ca_intermediate_cert_path](vars/main.yml#L24) | str | `{{ hashistack_ca_intermediate_dir }}/ca.crt` | n/a | n/a | +| [hashistack_ca_consul_dir](vars/main.yml#L26) | str | `{{ hashistack_ca_directory }}/consul/{{ inventory_hostname }}` | n/a | n/a | +| [hashistack_ca_consul_key_path](vars/main.yml#L27) | str | `{{ hashistack_ca_consul_dir }}/cert.key` | n/a | n/a | +| [hashistack_ca_consul_cert_path](vars/main.yml#L28) | str | `{{ hashistack_ca_consul_dir }}/cert.crt` | n/a | n/a | +| [hashistack_ca_consul_fullchain_path](vars/main.yml#L29) | str | `{{ hashistack_ca_consul_dir }}/fullchain.crt` | n/a | n/a | +| [hashistack_ca_nomad_dir](vars/main.yml#L31) | str | `{{ hashistack_ca_directory }}/nomad/{{ inventory_hostname }}` | n/a | n/a | +| [hashistack_ca_nomad_key_path](vars/main.yml#L32) | str | `{{ hashistack_ca_nomad_dir }}/cert.key` | n/a | n/a | +| [hashistack_ca_nomad_cert_path](vars/main.yml#L33) | str | `{{ hashistack_ca_nomad_dir }}/cert.crt` | n/a | n/a | +| [hashistack_ca_nomad_fullchain_path](vars/main.yml#L34) | str | `{{ hashistack_ca_nomad_dir }}/fullchain.crt` | n/a | n/a | +| [hashistack_ca_vault_dir](vars/main.yml#L36) | str | `{{ hashistack_ca_directory }}/vault/{{ inventory_hostname }}` | n/a | n/a | +| [hashistack_ca_vault_key_path](vars/main.yml#L37) | str | `{{ hashistack_ca_vault_dir }}/cert.key` | n/a | n/a | +| [hashistack_ca_vault_cert_path](vars/main.yml#L38) | str | `{{ hashistack_ca_vault_dir }}/cert.crt` | n/a | n/a | +| [hashistack_ca_vault_fullchain_path](vars/main.yml#L39) | str | `{{ hashistack_ca_vault_dir }}/fullchain.crt` | n/a | n/a | + + +### Tasks + + +#### File: tasks/prepare_ca_to_copy.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| CA \| Check if CA directory exists | ansible.builtin.stat | False | +| CA \| Find custom CA certificates to copy | ansible.builtin.find | True | +| CA \| Ensure public CA directory exists | ansible.builtin.file | False | +| CA \| Copy root CA certificates | ansible.builtin.copy | True | + +#### File: tasks/main.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| CA \| Import generate_root.yml | ansible.builtin.include_tasks | True | +| CA \| Import generate_intermediate.yml | ansible.builtin.include_tasks | True | +| CA \| Import renew_root.yml | ansible.builtin.include_tasks | True | +| CA \| Import renew_intermediate.yml | ansible.builtin.include_tasks | True | +| CA \| Import prepare_ca_to_copy.yml | ansible.builtin.include_tasks | False | +| CA \| Import cleanup_backups.yml | ansible.builtin.include_tasks | False | +| Consul leaf certificates \| Import generate/generate_consul.yml | ansible.builtin.include_tasks | True | +| Nomad leaf certificates \| Import generate/generate_nomad.yml | ansible.builtin.include_tasks | True | +| Vault leaf certificates \| Import generate/generate_vault.yml | ansible.builtin.include_tasks | True | +| Consul leaf certificates \| Import renew_consul.yml | ansible.builtin.include_tasks | True | + +#### File: tasks/cleanup_backups.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Cleanup \| Check if root CA backup directory exists | ansible.builtin.stat | False | +| Cleanup \| Check if intermediate CA backup directory exists | ansible.builtin.stat | False | +| Cleanup \| Root CA backups | block | True | +| Root CA \| Find root CA backup certificates | ansible.builtin.find | False | +| Root CA \| Check expiration for root CA backup certificates | when | True | +| Root CA \| Remove expired root CA backup certificates | when | True | +| Cleanup \| Intermediate CA backups | block | True | +| Intermediate CA \| Find intermediate CA backup certificates | ansible.builtin.find | False | +| Intermediate CA \| Check expiration for intermediate CA backup certificates | when | True | +| Intermediate CA \| Remove expired intermediate CA backup certificates | when | True | + +#### File: tasks/generate/generate_consul.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul leaf certificates \| Create certificate directory in for consul servers | ansible.builtin.file | False | +| Consul leaf certificates \| Create Consul certificates | block | False | +| Consul leaf certificates \| Create Consul certificate keys | community.crypto.openssl_privatekey | False | +| Consul leaf certificates \| Create CSRs for Consul servers | community.crypto.openssl_csr_pipe | False | +| Consul leaf certificates \| Sign certificates with internal CA | community.crypto.x509_certificate | False | +| Consul leaf certificates \| Generate fullchain certificate | block | False | +| Consul leaf certificates \| Read content of root ca certificate | ansible.builtin.slurp | False | +| Consul leaf certificates \| Read content of intermediate ca certificate | ansible.builtin.slurp | False | +| Consul leaf certificates \| Read content of leaf certificate | ansible.builtin.slurp | False | +| Consul leaf certificates \| Concatenate certificates | ansible.builtin.copy | False | + +#### File: tasks/generate/generate_intermediate.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Intermediate CA \| Create temporary cert directory in {{ hashistack_ca_directory }}/intermediate | ansible.builtin.file | False | +| Intermediate CA \| Generate internal certificates | block | False | +| Intermediate CA \| Create intermediate CA private key | community.crypto.openssl_privatekey | False | +| Intermediate CA \| Create intermediate CA signing request | community.crypto.openssl_csr_pipe | False | +| Intermediate CA \| Create signed intermediate CA certificate from CSR | community.crypto.x509_certificate | False | + +#### File: tasks/generate/generate_nomad.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad leaf certificates \| Create certificate directory in for nomad servers | ansible.builtin.file | False | +| Nomad leaf certificates \| Create Nomad certificates | block | False | +| Nomad leaf certificates \| Create Nomad certificate keys | community.crypto.openssl_privatekey | False | +| Nomad leaf certificates \| Create CSRs for Nomad servers | community.crypto.openssl_csr_pipe | False | +| Nomad leaf certificates \| Sign certificates with internal CA | community.crypto.x509_certificate | False | +| Nomad leaf certificates \| Generate fullchain certificate | block | False | +| Nomad leaf certificates \| Read content of root ca certificate | ansible.builtin.slurp | False | +| Nomad leaf certificates \| Read content of intermediate ca certificate | ansible.builtin.slurp | False | +| Nomad leaf certificates \| Read content of leaf certificate | ansible.builtin.slurp | False | +| Nomad leaf certificates \| Concatenate certificates | ansible.builtin.copy | False | + +#### File: tasks/generate/generate_vault.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault leaf certificates \| Create certificate directory in for vault servers | ansible.builtin.file | False | +| Vault leaf certificates \| Create Vault certificates | block | False | +| Vault leaf certificates \| Create Vault certificate keys | community.crypto.openssl_privatekey | False | +| Vault leaf certificates \| Create CSRs for Vault servers | community.crypto.openssl_csr_pipe | False | +| Vault leaf certificates \| Sign certificates with internal CA | community.crypto.x509_certificate | False | +| Vault leaf certificates \| Generate fullchain certificate | block | False | +| Vault leaf certificates \| Read content of root ca certificate | ansible.builtin.slurp | False | +| Vault leaf certificates \| Read content of intermediate ca certificate | ansible.builtin.slurp | False | +| Vault leaf certificates \| Read content of leaf certificate | ansible.builtin.slurp | False | +| Vault leaf certificates \| Concatenate certificates | ansible.builtin.copy | False | + +#### File: tasks/generate/generate_root.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Root CA \| Create temporary cert directory in {{ hashistack_ca_directory }} | ansible.builtin.file | False | +| Root CA \| Generate root Authority | block | False | +| Root CA \| Create CA private key | community.crypto.openssl_privatekey | False | +| Root CA \| Create CA signing request | community.crypto.openssl_csr_pipe | False | +| Root CA \| Create self-signed CA certificate from CSR | community.crypto.x509_certificate | False | +| Root CA \| Create self-signed CA certificate from CSR | community.crypto.x509_certificate | False | + +#### File: tasks/renew/renew_root.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Root CA \| Check if root CA certificate exists | ansible.builtin.stat | False | +| Root CA \| Check CA for renewal | block | True | +| Root CA \| Get root CA certificate expiration date | community.crypto.x509_certificate_info | False | +| Root CA \| Check if root CA certificate is expiring within the threshold | ansible.builtin.set_fact | False | +| Root CA \| Renew CA if expiring soon | block | True | +| Root CA \| Create backup directory for root CA | ansible.builtin.file | False | +| Root CA \| Format expiration date for backup | ansible.builtin.set_fact | False | +| Root CA \| Rename existing root CA certificate | ansible.builtin.command | False | +| Root CA \| Remove existing root CA key | ansible.builtin.file | False | +| Root CA \| Generate new root CA if renaming was successful | ansible.builtin.include_tasks | False | +| Root CA \| Generate new intermediate CA | ansible.builtin.include_tasks | False | + +#### File: tasks/renew/renew_consul.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Consul leaf certificates \| Check if certificate exists | ansible.builtin.stat | False | +| Consul leaf certificates \| Check if intermediate CA certificate exists | ansible.builtin.stat | False | +| Consul leaf certificates \| Check certificate for renewal | block | True | +| Consul leaf certificates \| Get certificate expiration date | community.crypto.x509_certificate_info | False | +| Intermediate CA \| Get intermediate CA certificate info | community.crypto.x509_certificate_info | False | +| Consul leaf certificates \| Check if certificate is expiring within the threshold | ansible.builtin.set_fact | False | +| Consul leaf certificates \| Check if intermediate CA has been renewed | ansible.builtin.set_fact | False | +| Consul leaf certificates \| Renew certificate if expiring soon or intermediate CA has been renewed | block | True | +| Consul leaf certificates \| Remove old certificate before renewal | ansible.builtin.file | False | +| Consul leaf certificates \| Remove old certificate key before renewal | ansible.builtin.file | False | +| Consul leaf certificates \| Generate new consul leaf certificate | ansible.builtin.include_tasks | False | + +#### File: tasks/renew/renew_nomad.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad leaf certificates \| Check if certificate exists | ansible.builtin.stat | False | +| Nomad leaf certificates \| Check if intermediate CA certificate exists | ansible.builtin.stat | False | +| Nomad leaf certificates \| Check certificate for renewal | block | True | +| Nomad leaf certificates \| Get certificate expiration date | community.crypto.x509_certificate_info | False | +| Intermediate CA \| Get intermediate CA certificate info | community.crypto.x509_certificate_info | False | +| Nomad leaf certificates \| Check if certificate is expiring within the threshold | ansible.builtin.set_fact | False | +| Nomad leaf certificates \| Check if intermediate CA has been renewed | ansible.builtin.set_fact | False | +| Nomad leaf certificates \| Renew certificate if expiring soon or intermediate CA has been renewed | block | True | +| Nomad leaf certificates \| Remove old certificate before renewal | ansible.builtin.file | False | +| Nomad leaf certificates \| Remove old certificate key before renewal | ansible.builtin.file | False | +| Nomad leaf certificates \| Generate new nomad leaf certificate | ansible.builtin.include_tasks | False | + +#### File: tasks/renew/renew_intermediate.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Intermediate CA \| Check if intermediate CA certificate exists | ansible.builtin.stat | False | +| Intermediate CA \| Check if root CA certificate exists | ansible.builtin.stat | False | +| Intermediate CA \| Check CA for renewal | block | True | +| Intermediate CA \| Get intermediate CA certificate expiration date | community.crypto.x509_certificate_info | False | +| Root CA \| Get root CA certificate info | community.crypto.x509_certificate_info | False | +| Intermediate CA \| Check if intermediate CA certificate is expiring within the threshold | ansible.builtin.set_fact | False | +| Intermediate CA \| Check if root CA has been renewed | ansible.builtin.set_fact | False | +| Intermediate CA \| Renew CA if expiring soon or root CA has been renewed | block | True | +| Intermediate CA \| Create backup directory for intermediate CA | ansible.builtin.file | False | +| Intermediate CA \| Format expiration date for backup | ansible.builtin.set_fact | False | +| Intermediate CA \| Backup existing intermediate CA certificate | ansible.builtin.command | False | +| Intermediate CA \| Backup existing intermediate CA key | ansible.builtin.command | False | +| Intermediate CA \| Generate new intermediate CA if backups were successful | ansible.builtin.include_tasks | False | +| Intermediate CA \| Generate new consul leaf certificates | ansible.builtin.include_tasks | False | +| Intermediate CA \| Generate new nomad leaf certificates | ansible.builtin.include_tasks | False | +| Intermediate CA \| Generate new vault leaf certificates | ansible.builtin.include_tasks | False | + +#### File: tasks/renew/renew_vault.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault leaf certificates \| Check if certificate exists | ansible.builtin.stat | False | +| Vault leaf certificates \| Check if intermediate CA certificate exists | ansible.builtin.stat | False | +| Vault leaf certificates \| Check certificate for renewal | block | True | +| Vault leaf certificates \| Get certificate expiration date | community.crypto.x509_certificate_info | False | +| Intermediate CA \| Get intermediate CA certificate info | community.crypto.x509_certificate_info | False | +| Vault leaf certificates \| Check if certificate is expiring within the threshold | ansible.builtin.set_fact | False | +| Vault leaf certificates \| Check if intermediate CA has been renewed | ansible.builtin.set_fact | False | +| Vault leaf certificates \| Renew certificate if expiring soon or intermediate CA has been renewed | block | True | +| Vault leaf certificates \| Remove old certificate before renewal | ansible.builtin.file | False | +| Vault leaf certificates \| Remove old certificate key before renewal | ansible.builtin.file | False | +| Vault leaf certificates \| Generate new vault leaf certificate | ansible.builtin.include_tasks | False | + + + + + + + + + diff --git a/roles/nomad/.docsible b/roles/nomad/.docsible new file mode 100644 index 0000000..7713e0f --- /dev/null +++ b/roles/nomad/.docsible @@ -0,0 +1,13 @@ +aap_hub: null +automation_kind: null +category: null +critical: null +description: null +dt_dev: null +dt_prod: null +dt_update: 26/08/2024 +requester: null +subCategory: null +time_saving: null +users: null +version: null diff --git a/roles/nomad/README.md b/roles/nomad/README.md new file mode 100644 index 0000000..f05bde8 --- /dev/null +++ b/roles/nomad/README.md @@ -0,0 +1,221 @@ + + +# πŸ“ƒ Role overview + +## nomad + + + +Description: Install and configure hashicorp nomad for debian-based distros. + + +| Field | Value | +|--------------------- |-----------------| +| Readme update | 26/08/2024 | + + + + + + +### Defaults + +**These are static variables with lower priority** + +#### File: defaults/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [nomad_version](defaults/main.yml#L4) | str | `latest` | n/a | n/a | +| [nomad_start_service](defaults/main.yml#L5) | bool | `True` | n/a | n/a | +| [nomad_config_dir](defaults/main.yml#L6) | str | `/etc/nomad.d` | n/a | n/a | +| [nomad_data_dir](defaults/main.yml#L7) | str | `/opt/nomad` | n/a | n/a | +| [nomad_certs_dir](defaults/main.yml#L8) | str | `{{ nomad_config_dir }}/tls` | n/a | n/a | +| [nomad_logs_dir](defaults/main.yml#L9) | str | `/var/log/nomad` | n/a | n/a | +| [nomad_extra_files](defaults/main.yml#L11) | bool | `False` | n/a | n/a | +| [nomad_extra_files_list](defaults/main.yml#L12) | list | `[]` | n/a | n/a | +| [nomad_env_variables](defaults/main.yml#L14) | dict | `{}` | n/a | n/a | +| [nomad_extra_configuration](defaults/main.yml#L25) | dict | `{}` | n/a | n/a | +| [nomad_region](defaults/main.yml#L31) | str | `global` | n/a | n/a | +| [nomad_datacenter](defaults/main.yml#L32) | str | `dc1` | n/a | n/a | +| [nomad_bind_addr](defaults/main.yml#L38) | str | `0.0.0.0` | n/a | n/a | +| [nomad_advertise_addr](defaults/main.yml#L39) | str | `{{ ansible_default_ipv4.address }}` | n/a | n/a | +| [nomad_address_configuration](defaults/main.yml#L40) | dict | `{'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}}` | n/a | n/a | +| [nomad_autopilot_configuration](defaults/main.yml#L59) | dict | `{}` | n/a | n/a | +| [nomad_leave_on_interrupt](defaults/main.yml#L65) | bool | `False` | n/a | n/a | +| [nomad_leave_on_terminate](defaults/main.yml#L66) | bool | `False` | n/a | n/a | +| [nomad_enable_server](defaults/main.yml#L72) | bool | `True` | n/a | n/a | +| [nomad_server_bootstrap_expect](defaults/main.yml#L73) | int | `1` | n/a | n/a | +| [nomad_server_configuration](defaults/main.yml#L74) | dict | `{'enabled': '{{ nomad_enable_server }}', 'data_dir': '{{ nomad_data_dir }}/server', 'encrypt': "{{ 'mysupersecretgossipencryptionkey'\|b64encode }}", 'server_join': {'retry_join': ['{{ ansible_default_ipv4.address }}']}}` | n/a | n/a | +| [nomad_enable_client](defaults/main.yml#L86) | bool | `False` | n/a | n/a | +| [nomad_client_configuration](defaults/main.yml#L87) | dict | `{'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'}` | n/a | n/a | +| [nomad_ui_configuration](defaults/main.yml#L98) | dict | `{'enabled': '{{ nomad_enable_server }}'}` | n/a | n/a | +| [nomad_driver_enable_docker](defaults/main.yml#L105) | bool | `True` | n/a | n/a | +| [nomad_driver_enable_podman](defaults/main.yml#L106) | bool | `False` | n/a | n/a | +| [nomad_driver_enable_raw_exec](defaults/main.yml#L107) | bool | `False` | n/a | n/a | +| [nomad_driver_enable_java](defaults/main.yml#L108) | bool | `False` | n/a | n/a | +| [nomad_driver_enable_qemu](defaults/main.yml#L109) | bool | `False` | n/a | n/a | +| [nomad_driver_configuration](defaults/main.yml#L111) | dict | `{'raw_exec': {'enabled': False}}` | n/a | n/a | +| [nomad_driver_extra_configuration](defaults/main.yml#L115) | dict | `{}` | n/a | n/a | +| [nomad_log_level](defaults/main.yml#L121) | str | `info` | n/a | n/a | +| [nomad_enable_log_to_file](defaults/main.yml#L122) | bool | `False` | n/a | n/a | +| [nomad_log_to_file_configuration](defaults/main.yml#L123) | dict | `{'log_file': '{{ nomad_logs_dir }}/nomad.log', 'log_rotate_duration': '24h', 'log_rotate_max_files': 30}` | n/a | n/a | +| [nomad_acl_configuration](defaults/main.yml#L132) | dict | `{'enabled': False, 'token_ttl': '30s', 'policy_ttl': '60s', 'role_ttl': '60s'}` | n/a | n/a | +| [nomad_enable_tls](defaults/main.yml#L142) | bool | `False` | n/a | n/a | +| [nomad_tls_configuration](defaults/main.yml#L143) | dict | `{'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}` | n/a | n/a | +| [nomad_certificates_extra_files_dir](defaults/main.yml#L151) | list | `[]` | n/a | n/a | +| [nomad_telemetry_configuration](defaults/main.yml#L160) | dict | `{'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}` | n/a | n/a | +| [nomad_enable_consul_integration](defaults/main.yml#L174) | bool | `False` | n/a | n/a | +| [nomad_consul_integration_configuration](defaults/main.yml#L175) | dict | `{'address': '127.0.0.1:8500', 'auto_advertise': True, 'ssl': False, 'token': '', 'tags': []}` | n/a | n/a | +| [nomad_consul_integration_tls_configuration](defaults/main.yml#L182) | dict | `{'ca_file': '/etc/ssl/certs/ca-certificates.crt'}` | n/a | n/a | +| [nomad_consul_integration_server_configuration](defaults/main.yml#L185) | dict | `{'server_auto_join': True}` | n/a | n/a | +| [nomad_consul_integration_client_configuration](defaults/main.yml#L188) | dict | `{'client_auto_join': True, 'grpc_address': '127.0.0.1:8502'}` | n/a | n/a | +| [nomad_consul_integration_client_tls_configuration](defaults/main.yml#L192) | dict | `{'grpc_ca_file': '/etc/ssl/certs/ca-certificates.crt'}` | n/a | n/a | +| [nomad_enable_vault_integration](defaults/main.yml#L199) | bool | `False` | n/a | n/a | +| [nomad_vault_integration_configuration](defaults/main.yml#L200) | dict | `{}` | n/a | n/a | + + +### Vars + +**These are variables with higher priority** +#### File: vars/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [nomad_user](vars/main.yml#L3) | str | `nomad` | n/a | n/a | +| [nomad_group](vars/main.yml#L4) | str | `nomad` | n/a | n/a | +| [nomad_binary_path](vars/main.yml#L5) | str | `/usr/local/bin/nomad` | n/a | n/a | +| [nomad_deb_architecture_map](vars/main.yml#L6) | dict | `{'x86_64': 'amd64', 'aarch64': 'arm64', 'armv7l': 'arm', 'armv6l': 'arm'}` | n/a | n/a | +| [nomad_architecture](vars/main.yml#L11) | str | `{{ nomad_deb_architecture_map[ansible_architecture] \| default(ansible_architecture) }}` | n/a | n/a | +| [nomad_service_name](vars/main.yml#L12) | str | `nomad` | n/a | n/a | +| [nomad_github_api](vars/main.yml#L13) | str | `https://api.github.com/repos` | n/a | n/a | +| [nomad_github_project](vars/main.yml#L14) | str | `hashicorp/nomad` | n/a | n/a | +| [nomad_github_url](vars/main.yml#L15) | str | `https://github.com` | n/a | n/a | +| [nomad_repository_url](vars/main.yml#L16) | str | `https://releases.hashicorp.com/nomad` | n/a | n/a | +| [nomad_configuration](vars/main.yml#L18) | dict | `{'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 }}'}` | n/a | n/a | +| [nomad_configuration_string](vars/main.yml#L30) | str | `` | n/a | n/a | + + +### Tasks + + +#### File: tasks/recursive_copy_extra_dirs.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad \| Ensure destination directory exists | ansible.builtin.file | False | +| Nomad \| Create extra directory sources | ansible.builtin.file | True | +| Nomad \| Template extra directory sources | ansible.builtin.template | True | + +#### File: tasks/merge_variables.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad \| Merge stringified configuration | vars | False | +| Nomad \| Merge addresses configuration | vars | False | +| Nomad \| Merge consul integration configuration | block | True | +| Nomad \| Merge consul tls configuration | block | True | +| Nomad \| Merge consul default client configuration | vars | False | +| Nomad \| Merge consul configuration for nomad servers | block | True | +| Nomad \| Merge consul default server configuration | vars | False | +| Nomad \| Merge consul configuration for nomad clients | block | True | +| Nomad \| Merge consul default client configuration | vars | False | +| Nomad \| Merge consul tls client configuration | vars | True | +| Nomad \| Merge consul block into main configuration | vars | False | +| Nomad \| Merge TLS configuration | block | True | +| Nomad \| Merge TLS configuration | vars | False | +| Nomad \| Add certificates directory to extra_files_dir | ansible.builtin.set_fact | False | +| Nomad \| Merge plugin configuration | vars | True | +| Nomad \| Merge extra configuration settings | vars | False | +| Nomad \| Merge log to file configuration | vars | True | +| Nomad \| Merge telemetry configuration | vars | False | + +#### File: tasks/main.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad \| Set reload-check & restart-check variable | ansible.builtin.set_fact | False | +| Nomad \| Import merge_variables.yml | ansible.builtin.include_tasks | False | +| Nomad \| Import prerequisites.yml | ansible.builtin.include_tasks | False | +| Nomad \| Import install.yml | ansible.builtin.include_tasks | False | +| Nomad \| Import configure.yml | ansible.builtin.include_tasks | False | +| Nomad \| Populate service facts | ansible.builtin.service_facts | False | +| Nomad \| Set restart-check variable | ansible.builtin.set_fact | True | +| Nomad \| Enable service: {{ nomad_service_name }} | ansible.builtin.service | False | +| Nomad \| Reload systemd daemon | ansible.builtin.systemd | True | +| Nomad \| Start service: {{ nomad_service_name }} | ansible.builtin.service | True | + +#### File: tasks/install.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad \| Get latest release of nomad | block | True | +| Nomad \| Get latest nomad release from github api | ansible.builtin.uri | False | +| Nomad \| Set wanted nomad version to latest tag | ansible.builtin.set_fact | False | +| Nomad \| Set wanted nomad version to {{ nomad_version }} | ansible.builtin.set_fact | True | +| Nomad \| Get current nomad version | block | False | +| Nomad \| Stat nomad version file | ansible.builtin.stat | False | +| Nomad \| Get current nomad version | ansible.builtin.slurp | True | +| Nomad \| Download and install nomad binary | block | True | +| Nomad \| Set nomad package name to download | ansible.builtin.set_fact | False | +| Nomad \| Download checksum file for nomad archive | ansible.builtin.get_url | False | +| Nomad \| Extract correct checksum from checksum file | ansible.builtin.command | False | +| Nomad \| Parse the expected checksum | ansible.builtin.set_fact | False | +| Nomad \| Download nomad binary archive | ansible.builtin.get_url | False | +| Nomad \| Create temporary directory for archive decompression | ansible.builtin.file | False | +| Nomad \| Unpack nomad archive | ansible.builtin.unarchive | False | +| Nomad \| Copy nomad binary to {{ nomad_binary_path }} | ansible.builtin.copy | False | +| Nomad \| Update nomad version file | ansible.builtin.copy | False | +| Nomad \| Set restart-check variable | ansible.builtin.set_fact | False | +| Nomad \| Cleanup temporary directory | ansible.builtin.file | False | +| Nomad \| Copy systemd service file for nomad | ansible.builtin.template | False | +| Nomad \| Set reload-check & restart-check variable | ansible.builtin.set_fact | True | + +#### File: tasks/prerequisites.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad \| Create group {{ nomad_group }} | ansible.builtin.group | False | +| Nomad \| Create user {{ nomad_user }} | ansible.builtin.user | False | +| Nomad \| Create directory {{ nomad_config_dir }} | ansible.builtin.file | False | +| Nomad \| Create directory {{ nomad_data_dir }} | ansible.builtin.file | False | +| Nomad \| Create directory {{ nomad_certs_dir }} | ansible.builtin.file | False | +| Nomad \| Create directory {{ nomad_logs_dir }} | ansible.builtin.file | True | + +#### File: tasks/configure.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Nomad \| Create nomad.env | ansible.builtin.template | False | +| Nomad \| Copy nomad.json template | ansible.builtin.template | False | +| Nomad \| Set restart-check variable | ansible.builtin.set_fact | True | +| Nomad \| Copy extra configuration files | block | True | +| Nomad \| Get extra file types | ansible.builtin.stat | False | +| Nomad \| Set list for file sources | vars | True | +| Nomad \| Set list for directory sources | vars | True | +| Nomad \| Template extra file sources | ansible.builtin.template | True | +| Nomad \| Template extra directory sources | ansible.builtin.include_tasks | True | + + + + + + + +## Author Information +Bertrand Lanson + +#### License + +license (BSD, MIT) + +#### Minimum Ansible Version + +2.10 + +#### Platforms + +- **Ubuntu**: ['focal', 'jammy', 'noble'] +- **Debian**: ['bullseye', 'bookworm'] + + diff --git a/roles/vault/.docsible b/roles/vault/.docsible new file mode 100644 index 0000000..7713e0f --- /dev/null +++ b/roles/vault/.docsible @@ -0,0 +1,13 @@ +aap_hub: null +automation_kind: null +category: null +critical: null +description: null +dt_dev: null +dt_prod: null +dt_update: 26/08/2024 +requester: null +subCategory: null +time_saving: null +users: null +version: null diff --git a/roles/vault/README.md b/roles/vault/README.md new file mode 100644 index 0000000..e26d2a1 --- /dev/null +++ b/roles/vault/README.md @@ -0,0 +1,193 @@ + + +# πŸ“ƒ Role overview + +## vault + + + +Description: Install and configure hashicorp vault for debian-based distros. + + +| Field | Value | +|--------------------- |-----------------| +| Readme update | 26/08/2024 | + + + + + + +### Defaults + +**These are static variables with lower priority** + +#### File: defaults/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [vault_version](defaults/main.yml#L3) | str | `latest` | n/a | n/a | +| [vault_start_service](defaults/main.yml#L4) | bool | `True` | n/a | n/a | +| [vault_config_dir](defaults/main.yml#L5) | str | `/etc/vault.d` | n/a | n/a | +| [vault_data_dir](defaults/main.yml#L6) | str | `/opt/vault` | n/a | n/a | +| [vault_certs_dir](defaults/main.yml#L7) | str | `{{ vault_config_dir }}/tls` | n/a | n/a | +| [vault_logs_dir](defaults/main.yml#L8) | str | `/var/log/vault` | n/a | n/a | +| [vault_extra_files](defaults/main.yml#L10) | bool | `False` | n/a | n/a | +| [vault_extra_files_list](defaults/main.yml#L11) | list | `[]` | n/a | n/a | +| [vault_env_variables](defaults/main.yml#L13) | dict | `{}` | n/a | n/a | +| [vault_extra_configuration](defaults/main.yml#L24) | dict | `{}` | n/a | n/a | +| [vault_cluster_name](defaults/main.yml#L30) | str | `vault` | n/a | n/a | +| [vault_bind_addr](defaults/main.yml#L31) | str | `0.0.0.0` | n/a | n/a | +| [vault_cluster_addr](defaults/main.yml#L32) | str | `{{ ansible_default_ipv4.address }}` | n/a | n/a | +| [vault_enable_ui](defaults/main.yml#L33) | bool | `True` | n/a | n/a | +| [vault_disable_mlock](defaults/main.yml#L34) | bool | `False` | n/a | n/a | +| [vault_disable_cache](defaults/main.yml#L35) | bool | `False` | n/a | n/a | +| [vault_storage_configuration](defaults/main.yml#L41) | dict | `{'file': {'path': '{{ vault_data_dir }}'}}` | n/a | n/a | +| [vault_enable_tls](defaults/main.yml#L49) | bool | `False` | n/a | n/a | +| [vault_listener_configuration](defaults/main.yml#L50) | list | `[{'tcp': {'address': '{{ vault_cluster_addr }}:8200', 'tls_disable': True}}]` | n/a | n/a | +| [vault_tls_listener_configuration](defaults/main.yml#L55) | list | `[{'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}}]` | n/a | n/a | +| [vault_certificates_extra_files_dir](defaults/main.yml#L62) | list | `[]` | n/a | n/a | +| [vault_extra_listener_configuration](defaults/main.yml#L67) | list | `[]` | n/a | n/a | +| [vault_enable_service_registration](defaults/main.yml#L73) | bool | `False` | n/a | n/a | +| [vault_service_registration_configuration](defaults/main.yml#L74) | dict | `{'consul': {'address': '127.0.0.1:8500', 'scheme': 'http', 'token': ''}}` | n/a | n/a | +| [vault_enable_plugins](defaults/main.yml#L84) | bool | `False` | n/a | n/a | +| [vault_plugins_directory](defaults/main.yml#L85) | str | `{{ vault_config_dir }}/plugins` | n/a | n/a | +| [vault_log_level](defaults/main.yml#L91) | str | `info` | n/a | n/a | +| [vault_enable_log_to_file](defaults/main.yml#L92) | bool | `False` | n/a | n/a | +| [vault_log_to_file_configuration](defaults/main.yml#L93) | dict | `{'log_file': '{{ vault_logs_dir }}/vault.log', 'log_rotate_duration': '24h', 'log_rotate_max_files': 30}` | n/a | n/a | + + +### Vars + +**These are variables with higher priority** +#### File: vars/main.yml + +| Var | Type | Value |Required | Title | +|--------------|--------------|-------------|-------------|-------------| +| [vault_user](vars/main.yml#L3) | str | `vault` | n/a | n/a | +| [vault_group](vars/main.yml#L4) | str | `vault` | n/a | n/a | +| [vault_binary_path](vars/main.yml#L5) | str | `/usr/local/bin/vault` | n/a | n/a | +| [vault_deb_architecture_map](vars/main.yml#L6) | dict | `{'x86_64': 'amd64', 'aarch64': 'arm64', 'armv7l': 'arm', 'armv6l': 'arm'}` | n/a | n/a | +| [vault_architecture](vars/main.yml#L11) | str | `{{ vault_deb_architecture_map[ansible_architecture] \| default(ansible_architecture) }}` | n/a | n/a | +| [vault_service_name](vars/main.yml#L12) | str | `vault` | n/a | n/a | +| [vault_github_api](vars/main.yml#L13) | str | `https://api.github.com/repos` | n/a | n/a | +| [vault_github_project](vars/main.yml#L14) | str | `hashicorp/vault` | n/a | n/a | +| [vault_github_url](vars/main.yml#L15) | str | `https://github.com` | n/a | n/a | +| [vault_repository_url](vars/main.yml#L16) | str | `https://releases.hashicorp.com/vault` | n/a | n/a | +| [vault_configuration](vars/main.yml#L18) | dict | `{'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 }}'}` | n/a | n/a | + + +### Tasks + + +#### File: tasks/recursive_copy_extra_dirs.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault \| Ensure destination directory exists | ansible.builtin.file | False | +| Vault \| Create extra directory sources | ansible.builtin.file | True | +| Vault \| Template extra directory sources | ansible.builtin.template | True | + +#### File: tasks/merge_variables.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault \| Merge listener configuration | block | False | +| Vault \| Merge tls listener configuration | vars | True | +| Vault \| Merge extra listener configuration | vars | False | +| Vault \| Add certificates directory to extra_files_dir | ansible.builtin.set_fact | False | +| Vault \| Merge service registration configuration | vars | True | +| Vault \| Merge plugins configuration | vars | True | +| Vault \| Merge logging configuration | vars | True | +| Vault \| Merge extra configuration settings | vars | True | + +#### File: tasks/main.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault \| Set reload-check & restart-check variable | ansible.builtin.set_fact | False | +| Vault \| Import merge_variables.yml | ansible.builtin.include_tasks | False | +| Vault \| Import prerequisites.yml | ansible.builtin.include_tasks | False | +| Vault \| Import install.yml | ansible.builtin.include_tasks | False | +| Vault \| Import configure.yml | ansible.builtin.include_tasks | False | +| Vault \| Populate service facts | ansible.builtin.service_facts | False | +| Vault \| Set restart-check variable | ansible.builtin.set_fact | True | +| Vault \| Enable service: {{ vault_service_name }} | ansible.builtin.service | False | +| Vault \| Reload systemd daemon | ansible.builtin.systemd | True | +| Vault \| Start service: {{ vault_service_name }} | ansible.builtin.service | True | + +#### File: tasks/install.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault \| Get latest release of vault | block | True | +| Vault \| Get latest vault release from github api | ansible.builtin.uri | False | +| Vault \| Set wanted vault version to latest tag | ansible.builtin.set_fact | False | +| Vault \| Set wanted vault version to {{ vault_version }} | ansible.builtin.set_fact | True | +| Vault \| Get current vault version | block | False | +| Vault \| Stat vault version file | ansible.builtin.stat | False | +| Vault \| Get current vault version | ansible.builtin.slurp | True | +| Vault \| Download and install vault binary | block | True | +| Vault \| Set vault package name to download | ansible.builtin.set_fact | False | +| Vault \| Download checksum file for vault archive | ansible.builtin.get_url | False | +| Vault \| Extract correct checksum from checksum file | ansible.builtin.command | False | +| Vault \| Parse the expected checksum | ansible.builtin.set_fact | False | +| Vault \| Download vault binary archive | ansible.builtin.get_url | False | +| Vault \| Create temporary directory for archive decompression | ansible.builtin.file | False | +| Vault \| Unpack vault archive | ansible.builtin.unarchive | False | +| Vault \| Copy vault binary to {{ vault_binary_path }} | ansible.builtin.copy | False | +| Vault \| Update vault version file | ansible.builtin.copy | False | +| Vault \| Set restart-check variable | ansible.builtin.set_fact | False | +| Vault \| Cleanup temporary directory | ansible.builtin.file | False | +| Vault \| Copy systemd service file for vault | ansible.builtin.template | False | +| Vault \| Set reload-check & restart-check variable | ansible.builtin.set_fact | True | +| Vault \| Copy systemd service file for vault | ansible.builtin.template | False | + +#### File: tasks/prerequisites.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault \| Create group {{ vault_group }} | ansible.builtin.group | False | +| Vault \| Create user {{ vault_user }} | ansible.builtin.user | False | +| Vault \| Create directory {{ vault_config_dir }} | ansible.builtin.file | False | +| Vault \| Create directory {{ vault_data_dir}} | ansible.builtin.file | False | +| Vault \| Create directory {{ vault_certs_dir }} | ansible.builtin.file | False | +| Vault \| Create directory {{ vault_logs_dir }} | ansible.builtin.file | True | + +#### File: tasks/configure.yml + +| Name | Module | Has Conditions | +| ---- | ------ | --------- | +| Vault \| Create vault.env | ansible.builtin.template | False | +| Vault \| Copy vault.json template | ansible.builtin.template | False | +| Vault \| Set restart-check variable | ansible.builtin.set_fact | True | +| Vault \| Copy extra configuration files | block | True | +| Vault \| Get extra file types | ansible.builtin.stat | False | +| Vault \| Set list for file sources | vars | True | +| Vault \| Set list for directory sources | vars | True | +| Vault \| Template extra file sources | ansible.builtin.template | True | +| Vault \| Template extra directory sources | ansible.builtin.include_tasks | True | + + + + + + + +## Author Information +Bertrand Lanson + +#### License + +license (BSD, MIT) + +#### Minimum Ansible Version + +2.10 + +#### Platforms + +- **Ubuntu**: ['focal', 'jammy', 'noble'] +- **Debian**: ['bullseye', 'bookworm'] + + From bb77c38d3ddb904a244ce381acf0d1f8eb4b36d8 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Mon, 26 Aug 2024 23:14:35 +0200 Subject: [PATCH 2/6] fix: remove duplicate tags for nomad tasks --- playbooks/tasks/nomad/nomad_deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/playbooks/tasks/nomad/nomad_deploy.yml b/playbooks/tasks/nomad/nomad_deploy.yml index 543f3a8..7bf78ba 100644 --- a/playbooks/tasks/nomad/nomad_deploy.yml +++ b/playbooks/tasks/nomad/nomad_deploy.yml @@ -7,7 +7,6 @@ when: - nomad_enable_server tags: - - nomad - nomad_servers - name: "Deploy Nomad Clients" @@ -17,5 +16,4 @@ - nomad_enable_client - not nomad_enable_server tags: - - nomad - nomad_clients From 23c99407ba65257469d0f09cf245d89a7aaba49a Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Tue, 27 Aug 2024 22:30:10 +0200 Subject: [PATCH 3/6] feat: add some templating for nomad haproxy job --- roles/hashistack_ingress/.docsible | 13 ++ roles/hashistack_ingress/README.md | 52 ++++++ roles/hashistack_ingress/defaults/main.yml | 45 +++++ roles/hashistack_ingress/handlers/main.yml | 2 + roles/hashistack_ingress/meta/main.yml | 28 ++++ roles/hashistack_ingress/tasks/main.yml | 2 + .../templates/chk_haproxy.sh.j2 | 1 + .../templates/haproxy.cfg.j2 | 31 ++++ .../templates/ingress.job.hcl.j2 | 155 ++++++++++++++++++ .../templates/keepalived.conf.j2 | 37 +++++ roles/hashistack_ingress/vars/main.yml | 28 ++++ 11 files changed, 394 insertions(+) create mode 100644 roles/hashistack_ingress/.docsible create mode 100644 roles/hashistack_ingress/README.md create mode 100644 roles/hashistack_ingress/defaults/main.yml create mode 100644 roles/hashistack_ingress/handlers/main.yml create mode 100644 roles/hashistack_ingress/meta/main.yml create mode 100644 roles/hashistack_ingress/tasks/main.yml create mode 100644 roles/hashistack_ingress/templates/chk_haproxy.sh.j2 create mode 100644 roles/hashistack_ingress/templates/haproxy.cfg.j2 create mode 100644 roles/hashistack_ingress/templates/ingress.job.hcl.j2 create mode 100644 roles/hashistack_ingress/templates/keepalived.conf.j2 create mode 100644 roles/hashistack_ingress/vars/main.yml diff --git a/roles/hashistack_ingress/.docsible b/roles/hashistack_ingress/.docsible new file mode 100644 index 0000000..7713e0f --- /dev/null +++ b/roles/hashistack_ingress/.docsible @@ -0,0 +1,13 @@ +aap_hub: null +automation_kind: null +category: null +critical: null +description: null +dt_dev: null +dt_prod: null +dt_update: 26/08/2024 +requester: null +subCategory: null +time_saving: null +users: null +version: null diff --git a/roles/hashistack_ingress/README.md b/roles/hashistack_ingress/README.md new file mode 100644 index 0000000..53897f3 --- /dev/null +++ b/roles/hashistack_ingress/README.md @@ -0,0 +1,52 @@ + + +# πŸ“ƒ Role overview + +## hashistack_ingress + + + +Description: Deploys an ingress reverse-proxy on a hashistack-ansible managed nomad cluster + + +| Field | Value | +|---------------|------------| +| Readme update | 26/08/2024 | + + + + + + + + + + + + +### Tasks + + + + + + + + +## Author Information +Bertrand Lanson + +#### License + +license (BSD, MIT) + +#### Minimum Ansible Version + +2.10 + +#### Platforms + +- **Ubuntu**: ['focal', 'jammy', 'noble'] +- **Debian**: ['bullseye', 'bookworm'] + + diff --git a/roles/hashistack_ingress/defaults/main.yml b/roles/hashistack_ingress/defaults/main.yml new file mode 100644 index 0000000..ee7df4f --- /dev/null +++ b/roles/hashistack_ingress/defaults/main.yml @@ -0,0 +1,45 @@ +--- +# defaults file for hashistack_ingress +hashistack_ingress_nomad_api_addr: "http://127.0.0.1:4646" +hashistack_ingress_nomad_api_token: + +hashistack_ingress_job_name: HashistackHAProxyIngress +hashistack_ingress_datacenters: [] +hashistack_ingress_namespace: default +hashistack_ingress_replicas: 1 +hashistack_ingress_enable_consul_service: true + +hashistack_ingress_virtual_ip_keepalived_version: latest +hashistack_ingress_virtual_ip_addr: "192.168.1.1" +hashistack_ingress_virtual_ip_interface: eth0 +hashistack_ingress_virtual_ip_vrrp_interface: "{{ hashistack_ingress_virtual_ip_interface }}" +hashistack_ingress_virtual_ip_vrrp_router_id: 50 +hashistack_ingress_virtual_ip_vrrp_priority: 100 +hashistack_ingress_virtual_ip_vrrp_advertise_interval: 1 +hashistack_ingress_virtual_ip_vrrp_password: password + +hashistack_ingress_enable_http: true +hashistack_ingress_enable_https: false +hashistack_ingress_enable_prometheus_metrics: false +hashistack_ingress_enable_admin_interface: false +hashistack_ingress_admin_interface_password: password + +hashistack_ingress_virtual_ip_haproxy_version: latest +hashistack_ingress_haproxy_global: + - log /dev/log local0 + - log /dev/log local1 notice + - stats socket {{ deploy_haproxy_socket }} level admin + - chroot {{ deploy_haproxy_chroot }} + - daemon + - description hashistack haproxy +hashistack_ingress_haproxy_defaults: + - log global + - mode http + - option httplog + - option dontlognull + - timeout connect 5000 + - timeout client 5000 + - timeout server 5000 +hashistack_ingress_haproxy_frontends: [] +hashistack_ingress_haproxy_backends: [] +hashistack_ingress_haproxy_listen: [] diff --git a/roles/hashistack_ingress/handlers/main.yml b/roles/hashistack_ingress/handlers/main.yml new file mode 100644 index 0000000..81ae4df --- /dev/null +++ b/roles/hashistack_ingress/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for hashistack_ingress diff --git a/roles/hashistack_ingress/meta/main.yml b/roles/hashistack_ingress/meta/main.yml new file mode 100644 index 0000000..01ba49c --- /dev/null +++ b/roles/hashistack_ingress/meta/main.yml @@ -0,0 +1,28 @@ +--- +# meta file for hashistack_ingress +galaxy_info: + namespace: "ednz_cloud" + role_name: "hashistack_ingress" + author: "Bertrand Lanson" + description: "Deploys an ingress reverse-proxy on a hashistack-ansible managed nomad cluster" + 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" + - "haproxy" + - "ingress" + +dependencies: [] diff --git a/roles/hashistack_ingress/tasks/main.yml b/roles/hashistack_ingress/tasks/main.yml new file mode 100644 index 0000000..da865fa --- /dev/null +++ b/roles/hashistack_ingress/tasks/main.yml @@ -0,0 +1,2 @@ +--- +# task/main file for hashistack_ingress diff --git a/roles/hashistack_ingress/templates/chk_haproxy.sh.j2 b/roles/hashistack_ingress/templates/chk_haproxy.sh.j2 new file mode 100644 index 0000000..50d4884 --- /dev/null +++ b/roles/hashistack_ingress/templates/chk_haproxy.sh.j2 @@ -0,0 +1 @@ +#! /bin/sh diff --git a/roles/hashistack_ingress/templates/haproxy.cfg.j2 b/roles/hashistack_ingress/templates/haproxy.cfg.j2 new file mode 100644 index 0000000..bf43a0d --- /dev/null +++ b/roles/hashistack_ingress/templates/haproxy.cfg.j2 @@ -0,0 +1,31 @@ +# {{ ansible_managed }} +global +{% for option in hashistack_ingress_haproxy_global %} + {{ option }} +{% endfor %} + +defaults +{% for option in hashistack_ingress_haproxy_defaults %} + {{ option }} +{% endfor %} + +{% for frontend in hashistack_ingress_haproxy_frontends + hashistack_ingress_mandatory_frontends %} +frontend {{ frontend.name }} +{% for option in frontend.options %} + {{ option }} +{% endfor %} +{% endfor %} + +{% for backend in hashistack_ingress_haproxy_backends %} +backend {{ backend.name }} +{% for option in backend.options%} + {{ option }} +{% endfor %} +{% endfor %} + +{% for listen in hashistack_ingress_haproxy_listen %} +listen {{ listen.name }} +{% for option in listen.options %} + {{ option }} +{% endfor %} +{% endfor %} diff --git a/roles/hashistack_ingress/templates/ingress.job.hcl.j2 b/roles/hashistack_ingress/templates/ingress.job.hcl.j2 new file mode 100644 index 0000000..188e915 --- /dev/null +++ b/roles/hashistack_ingress/templates/ingress.job.hcl.j2 @@ -0,0 +1,155 @@ +job "{{ hashistack_ingress_job_name }}" { + datacenters = {{ hashistack_ingress_datacenters }} + type = "service" + priority = 85 + namespace = {{ hashistack_ingress_namespace }} + + group "haproxy" { + network { + mode = "bridge" + port "http" { + to = 80 + static = 80 + } + port "https" { + to = 443 + static = 443 + } + port "stats" { + to = 9000 + } + } + +{% if hashistack_ingress_enable_http %} + service { + name = "haproxy-http" + provider = "{{ "consul" if hashistack_ingress_enable_consul_service else "nomad"}}" + port = "http" + task = "loadbalancer" + check { + type = "http" + port = "stats" + path = "/health" + interval = "10s" + timeout = "2s" + } + tags = [] + } +{% endif %} + +{% if hashistack_ingress_enable_https %} + service { + name = "haproxy-https" + provider = "{{ "consul" if hashistack_ingress_enable_consul_service else "nomad"}}" + port = "https" + task = "loadbalancer" + check { + type = "http" + port = "stats" + path = "/health" + interval = "10s" + timeout = "2s" + } + tags = [] + } +{% endif %} + + service { + name = "haproxy-stats" + provider = "{{ "consul" if hashistack_ingress_enable_consul_service else "nomad"}}" + port = "stats" + task = "loadbalancer" + check { + type = "http" + port = "stats" + path = "/health" + interval = "10s" + timeout = "2s" + } + tags = [] + } + + + +{% if hashistack_ingress_enable_prometheus_metrics %} + service { + name = "loadbalancer-exporter" + port = "prometheus-exporter" + task = "loadbalancer" + tags = [] + } +{% endif %} + + task "keepalived" { + driver = "docker" + lifecycle { + hook = "poststart" + sidecar = true + } + config { + image = "{{ hashistack_ingress_keepalived_image }}:{{ hashistack_ingress_virtual_ip_keepalived_version }}" + network_mode = "host" + cap_add = [ + "NET_ADMIN", + "NET_BROADCAST", + "NET_RAW" + ] + mount { + type = "bind" + source = "secrets/keepalived.conf" + target = "/etc/keepalived/keepalived.conf" + } + mount { + type = "bind" + source = "secrets/chk_haproxy.sh" + target = "/etc/keepalived/scripts.d/chk_haproxy.sh" + } + mount { + type = "bind" + target = "/var/run/docker.sock" + source = "/var/run/docker.sock" + readonly = true + } + } + template { + data = <<-EOT +{% include "keepalived.conf.j2" %} +EOT + destination = "secrets/keepalived.conf" + } + template { + data = <<-EOT +{% include "chk_haproxy.sh.j2" %} +EOT + destination = "secrets/chk_haproxy.sh" + perms = "755" + } + resources { + cpu = 50 + memory = 10 + } + } + + task "loadbalancer" { + driver = "docker" + config { + image = "{{ hashistack_ingress_haproxy_image }}:{{ hashistack_ingress_virtual_ip_haproxy_version }}" + mount { + type = "bind" + source = "secrets/haproxy.cfg" + target = "/usr/local/etc/haproxy/haproxy.cfg" + } + } + template { + data = <<-EOT +{% include "haproxy.cfg.j2" %} +EOT + destination = "secrets/haproxy.cfg" + } + resources { + cpu = 128 + memory = 256 + } + } + } +} diff --git a/roles/hashistack_ingress/templates/keepalived.conf.j2 b/roles/hashistack_ingress/templates/keepalived.conf.j2 new file mode 100644 index 0000000..7a6a2f3 --- /dev/null +++ b/roles/hashistack_ingress/templates/keepalived.conf.j2 @@ -0,0 +1,37 @@ +global_defs { + script_user root + enable_script_security +} + +vrrp_script chk_haproxy { + script "/etc/keepalived/scripts.d/chk_haproxy.sh" + user root + interval 3 + weight 0 + rise 6 + fall 1 +} + +vrrp_instance haproxy { + interface {{ hashistack_ingress_virtual_ip_vrrp_interface }} + + state {{ hashistack_ingress_keepalived_init_state }} + virtual_router_id {{ hashistack_ingress_virtual_ip_vrrp_router_id }} + priority {{ hashistack_ingress_virtual_ip_vrrp_priority }} + advert_int {{ hashistack_ingress_virtual_ip_vrrp_advertise_interval }} + + authentication { + auth_type PASS + auth_pass {{ hashistack_ingress_virtual_ip_vrrp_password }} + } + + virtual_ipaddress { + {{ hashistack_ingress_virtual_ip_addr }}/32 dev {{ hashistack_ingress_virtual_ip_interface }} + } + + track_script { + chk_haproxy + } + + notify /etc/keepalived/scripts.d/notify.sh +} diff --git a/roles/hashistack_ingress/vars/main.yml b/roles/hashistack_ingress/vars/main.yml new file mode 100644 index 0000000..6426983 --- /dev/null +++ b/roles/hashistack_ingress/vars/main.yml @@ -0,0 +1,28 @@ +--- +# vars file for hashistack_ingress +hashistack_ingress_keepalived_image: ednxzu/keepalived +hashistack_ingress_haproxy_image: haproxytech/haproxy-debian + +hashistack_ingress_keepalived_init_state: BACKUP + +hashistack_ingress_template_haproxy_cfg: "{{ lookup('ansible.builtin.template', 'haproxy.cfg.j2') }}" +hashistack_ingress_template_keepalived_conf: "{{ lookup('ansible.builtin.template', 'keepalived.conf.j2') }}" +hashistack_ingress_template_chk_haproxy_sh: "{{ lookup('ansible.builtin.template', 'chk_haproxy.sh.j2') }}" + +hashistack_ingress_mandatory_frontends: + - name: monitoring + options: + - bind :9000 + - mode http + - option httpchk + - "{{'stats enable' if hashistack_ingress_enable_admin_interface else omit }}" + - "{{'stats uri /stats' if hashistack_ingress_enable_admin_interface else omit }}" + - "{{'stats refresh 30s' if hashistack_ingress_enable_admin_interface else omit }}" + - "{{'stats show-desc' if hashistack_ingress_enable_admin_interface else omit }}" + - "{{'stats show-legends' if hashistack_ingress_enable_admin_interface else omit }}" + - "{{'stats auth admin:'~hashistack_ingress_admin_interface_password if hashistack_ingress_enable_admin_interface else omit }}" + - http-check send meth GET uri /health ver HTTP/1.1 hdr Host localhost + - http-check expect status 200 + - acl health_check_ok nbsrv() ge 1 + - monitor-uri /health + - "{{'http-request use-service prometheus-exporter if { path /metrics }' if hashistack_ingress_enable_prometheus_metrics else omit }}" From 41d8254fa8420aa4308afe8490a301a26c3f836a Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Wed, 28 Aug 2024 21:18:06 +0200 Subject: [PATCH 4/6] feat: initial cool readme --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9c79c7..f87be2d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,48 @@ -# Ansible Collection - ednz_cloud.hashistack +# πŸš€ hashistack-ansible Collection -THIS REPOSITORY IS A WORK IN PROGRESS, IT IS NOWHERE NEAR FIT FOR PRODUCTION. +![Ansible Badge](https://img.shields.io/badge/Ansible-E00?logo=ansible&logoColor=fff&style=for-the-badge) +![HashiCorp Badge](https://img.shields.io/badge/HashiCorp-000?logo=hashicorp&logoColor=fff&style=for-the-badge) +![Nomad Badge](https://img.shields.io/badge/Nomad-00CA8E?logo=nomad&logoColor=fff&style=for-the-badge) +![Vault Badge](https://img.shields.io/badge/Vault-FFEC6E?logo=vault&logoColor=000&style=for-the-badge) +![Consul Badge](https://img.shields.io/badge/Consul-F24C53?logo=consul&logoColor=fff&style=for-the-badge) + +Welcome to hashistack-ansible! This Ansible collection is your one-stop solution for deploying and managing HashiCorp product clusters with ease. Whether you're setting up Vault, Consul, or Nomad, this collection automates the entire process, allowing you to focus on building and scaling your infrastructure without the hassle. Let's dive into what this collection offers! 🌟 + +## 🎯 Features + +### πŸ”§ Deploy from Scratch + +Set up a fully integrated HashiStack environment with Vault, Consul, and Nomad from the ground up. Our playbooks ensure seamless integration between these services, enabling you to hit the ground running without manual setup. + +### 🎯 Targeted and Global Updates + +Update specific parts of your infrastructure using tags to target individual components or perform global updates to keep your entire stack in sync. This flexibility ensures minimal downtime and smooth rollouts. + + +### πŸ” Comprehensive Certificate Authority Management + +Easily manage your cluster's certificates with playbooks that handle the creation, renewal, and distribution of certificates across your infrastructure. This includes Root CAs, intermediates, and service-specific leaf certificates, ensuring secure communication throughout your environment. + +### πŸ”„ Rolling Updates for High Availability + +Perform control plane upgrades with rolling updates, ensuring that your services stay online and available during the upgrade process. This minimizes risk and maximizes uptime. + +## πŸ› οΈ How to Get Started + +1. Install the Collection: + + ```shell + ansible-galaxy collection install ednz_cloud.hashistack:== + ``` + +2. Head to the [Quick-Start Guide](https://git.ednz.fr/ansible-collections/hashistack/wiki/quick_start) + + You'll get + +## πŸ§‘β€πŸ’» Contributions & Feedback + +We welcome contributions and feedback! If you encounter any issues or have suggestions for improvement, feel free to open an issue or submit a pull request. Let's make hashistack-ansible even better together! 🀝 + +## πŸ“„ License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. From 367d5481f6a8db302e017f64140806af8f737725 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Wed, 28 Aug 2024 23:22:22 +0200 Subject: [PATCH 5/6] feat: rename playbooks for certificates and credentials --- playbooks/certificates.yml | 21 ++++++++++ playbooks/credentials.yml | 85 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 playbooks/certificates.yml create mode 100644 playbooks/credentials.yml diff --git a/playbooks/certificates.yml b/playbooks/certificates.yml new file mode 100644 index 0000000..5da90dc --- /dev/null +++ b/playbooks/certificates.yml @@ -0,0 +1,21 @@ +--- +# hashistack generate certificates playbook +- name: "Generate certificates" + hosts: all, !deployment + strategy: linear + gather_facts: true + become: true + tasks: + - name: "Import variables" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.hashistack + tags: + - always + + - name: "Create Certificate Authority" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.hashistack_ca + apply: + delegate_to: localhost + tags: + - always diff --git a/playbooks/credentials.yml b/playbooks/credentials.yml new file mode 100644 index 0000000..a705d11 --- /dev/null +++ b/playbooks/credentials.yml @@ -0,0 +1,85 @@ +--- +# hashistack generate certificates playbook +- name: "Generate credentials" + hosts: deployment + strategy: linear + gather_facts: true + become: true + tasks: + - name: "Generate consul credentials" + block: + - name: "Generate consul gossip encryption key" + 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: + _consul_root_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + + - name: "Generate consul agents credentials" + ansible.builtin.set_fact: + _consul_agents_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + _consul_agents_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + + - name: "Generate consul vault credentials" + ansible.builtin.set_fact: + _consul_vault_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + _consul_vault_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + + - name: "Generate consul nomad server credentials" + ansible.builtin.set_fact: + _consul_nomad_server_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + _consul_nomad_server_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + + - name: "Generate consul nomad client credentials" + ansible.builtin.set_fact: + _consul_nomad_client_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + _consul_nomad_client_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + + - name: "Generate nomad credentials" + block: + - name: "Generate nomad gossip encryption key" + 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: + _nomad_root_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" + + - name: "Ensure secrets directory is created" + ansible.builtin.file: + path: "{{ hashistack_sub_configuration_directories['secrets'] }}" + state: directory + owner: "{{ lookup('env', 'USER') }}" + group: "{{ lookup('env', 'USER') }}" + mode: '0755' + + - name: "Write credentials file" + ansible.builtin.template: + src: templates/credentials.yml.j2 + dest: "{{ hashistack_sub_configuration_directories['secrets'] }}/{{ hashistack_configuration_credentials_vars_file }}" + owner: "{{ lookup('env', 'USER') }}" + group: "{{ lookup('env', 'USER') }}" + mode: '0644' From dc096e497d7c19cc106255095de9310530afd50a Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Thu, 29 Aug 2024 20:13:45 +0200 Subject: [PATCH 6/6] feat: redirect to wiki and add assets --- README.md | 10 +- assets/boundary_500x500.png | Bin 0 -> 12298 bytes assets/boundary_white_500x500.png | Bin 0 -> 11774 bytes assets/consul_500x500.png | Bin 0 -> 21011 bytes assets/consul_white_500x500.png | Bin 0 -> 15270 bytes assets/hashicorp_500x500.png | Bin 0 -> 13283 bytes assets/nomad_500x500.png | Bin 0 -> 12360 bytes assets/nomad_white_500x500.png | Bin 0 -> 11991 bytes assets/packer_500x500.png | Bin 0 -> 12595 bytes assets/packer_white_500x500.png | Bin 0 -> 12250 bytes assets/terraform_500x500.png | Bin 0 -> 14449 bytes assets/terraform_white_500x500.png | Bin 0 -> 11943 bytes assets/vagrant_500x500.png | Bin 0 -> 15814 bytes assets/vagrant_white_500x500.png | Bin 0 -> 12558 bytes assets/vault_500x500.png | Bin 0 -> 11927 bytes assets/vault_white_500x500.png | Bin 0 -> 11359 bytes assets/waypoint_500x500.png | Bin 0 -> 13987 bytes assets/waypoint_white_500x500.png | Bin 0 -> 13996 bytes docs/README.md | 1 + docs/architecture_guide.md.md | 97 ---------------- docs/consul_clusters.md | 3 - docs/extra_configuration.md | 1 - docs/general.md | 103 ----------------- docs/haproxy_servers.md | 104 ----------------- docs/nomad_clusters.md | 82 ------------- docs/quick_start.md | 154 ------------------------- docs/tls_guide.md | 6 - docs/vault_clusters.md | 107 ----------------- galaxy.yml | 3 +- molecule/no_tls_multi_node/prepare.yml | 2 +- molecule/tls_multi_node/prepare.yml | 2 +- playbooks/deploy.yml | 15 --- playbooks/generate_certs.yml | 21 ---- playbooks/generate_credentials.yml | 85 -------------- playbooks/group_vars/all/all.yml | 33 ++++++ playbooks/group_vars/all/consul.yml | 15 --- playbooks/group_vars/all/globals.yml | 6 - playbooks/group_vars/all/nomad.yml | 14 +-- playbooks/group_vars/all/vault.yml | 2 - playbooks/inventory/multinode.ini | 6 - 40 files changed, 43 insertions(+), 829 deletions(-) create mode 100755 assets/boundary_500x500.png create mode 100755 assets/boundary_white_500x500.png create mode 100755 assets/consul_500x500.png create mode 100755 assets/consul_white_500x500.png create mode 100755 assets/hashicorp_500x500.png create mode 100755 assets/nomad_500x500.png create mode 100755 assets/nomad_white_500x500.png create mode 100755 assets/packer_500x500.png create mode 100755 assets/packer_white_500x500.png create mode 100755 assets/terraform_500x500.png create mode 100755 assets/terraform_white_500x500.png create mode 100755 assets/vagrant_500x500.png create mode 100755 assets/vagrant_white_500x500.png create mode 100755 assets/vault_500x500.png create mode 100755 assets/vault_white_500x500.png create mode 100755 assets/waypoint_500x500.png create mode 100755 assets/waypoint_white_500x500.png create mode 100644 docs/README.md delete mode 100644 docs/architecture_guide.md.md delete mode 100644 docs/consul_clusters.md delete mode 100644 docs/extra_configuration.md delete mode 100644 docs/general.md delete mode 100644 docs/haproxy_servers.md delete mode 100644 docs/nomad_clusters.md delete mode 100644 docs/quick_start.md delete mode 100644 docs/tls_guide.md delete mode 100644 docs/vault_clusters.md delete mode 100644 playbooks/generate_certs.yml delete mode 100644 playbooks/generate_credentials.yml diff --git a/README.md b/README.md index f87be2d..c7d3f19 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,13 @@ Perform control plane upgrades with rolling updates, ensuring that your services ## πŸ› οΈ How to Get Started -1. Install the Collection: +1. Head to the [Quick-Start Guide](https://git.ednz.fr/ansible-collections/hashistack/wiki/02-quick-start) - ```shell - ansible-galaxy collection install ednz_cloud.hashistack:== - ``` + This should give you a good idea of how the collection works, and what it can do. -2. Head to the [Quick-Start Guide](https://git.ednz.fr/ansible-collections/hashistack/wiki/quick_start) +2. From there, you can follow the rest of the [Documentation](https://git.ednz.fr/ansible-collections/hashistack/wiki/01-introduction) - You'll get + This will allow you to customize your deployment to your exact needs ! ## πŸ§‘β€πŸ’» Contributions & Feedback diff --git a/assets/boundary_500x500.png b/assets/boundary_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..3d15612ba88ec444520f95a9bfea99686f876602 GIT binary patch literal 12298 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 literal 0 HcmV?d00001 diff --git a/assets/consul_white_500x500.png b/assets/consul_white_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..54db586169de892b3e23d45b87456d8590880c53 GIT binary patch literal 15270 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 literal 0 HcmV?d00001 diff --git a/assets/hashicorp_500x500.png b/assets/hashicorp_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..86761ffc0ed6bcb254bbb9f3de88ba9d642708a5 GIT binary patch literal 13283 zcmeHuc{tST`#(|*6@yBVeJfdx?1YT1vZX9pr;=2%WZ%Y6BT39bMb;)o*(UosL#w2i zh$&;K(2Qk>28EgVKJRfpo%8)(zw7$_^ZV!bz0M!!x;pc|pXa`xdwo66eM>%OXSI!A zjGv2(Yuk~-=Eu3XxEauYuub5Xu7S}TTwKyzN6bwfBfaRu*xP#=(&I0CZW2h4`FY?Y zJa+dnp^s_p&b+=>r_KfOpEe8H)UmaD_mK93e35;s5eauM-VqfA(x;sXDMe)ukdv*zK2AL8QX)sa3#6X$&o=ll;`I#mC! z6`OcrsH@UqJkWA39*o-Nb%KxaT)05Exf!$+ekh(a!P%dSM;tEA*mmVSY@GqTdCxk* zsSP|BqA7eEH$=_FjmX-tPH+h<)rH-cSr_LJEts<%SHd5UYQl{$*>z*;SV z`)%Z*3jZ8Vd2J$zt}iTm*Tjz6y*!#`!j4tDSSNQZ_fXx-hvBuUaXJYlMNjL^{9Ard z$_DdV_z0>f|T#T4trY_YLu9@}wtR4bZ&)TQ)YlX_y@^ZA~!q;Lj_ z#XxW+Nb~gX%#yxTYWmrUr;SKpx#2E`%xdFC8*B5Me}rZ7{exgXkCAMf=Q00of&xQa zggULborjyZR8@`pwPQlN3vWSM8*?RZP1U^rn+KL8j_*-#S*-V420RR5j_w|_hsK3@ zy%b31td22elBqGE!jl4bQJPlJt*|(F#aGw|#bf@LA4~WbVWs~Fj$zg<0dXC0quF(o z@AOy#^IWSa4=fb^sJ<~&+JQI0g-~6fd?^)ZgMD_T7N3J5b!Bb&Q@~#CduZ+UFuQ=H zxLEB6 zpAV=s*wPg54A=XSHza@>5nz(b^YQp>+d&)Cc+v@ssgk0|dFnWQX(g$XUjGd!$kmXc zZZ@e=u|?QDa~fJdAVR)+o43HroHgie>$?bi$X$lvu3}Ui=IrdJv=MS3ewu+setRip z8o#X;eBQ?pXWMrOc9To&4Yzee_PYDdCB~2MtnjZ?^E&-3Q=0BlH<{dRRZ(bjZ}4mI z0lR)BSyBKVdvGAsnG8JH1`(*iTB~*sYzbHAt(S$AsvNoLhjN1A!qUq>=sh>_7emx? zNrC!bG%9xcCK(%W@s zpnmOF-+bJ7RdJ1_yMMQj=!)vBWS@cGrt?%S$jkc)V2b|=bIfl1-{JPHKCb8BSzL1w8M`F&K=(g0&Myk}S3ukG)lmVSeVS6@z8Spui}qKj+9c;uW&r zwLwV}+Rrx>fNuzlDq8?<96o|;$Wve_YcBJQmDe|VNXsiUH?Hb8Ni<7DCnsO}vk3-| zu8ee~*|rc0=7Ih{y5xq0(`pV_8%{uu_GWXeOuXX4Tda0|V)0;}9K-eQ!~6v(7$!Cl zxeiSL_xe()LxrK9KIl7Z3)w2i!AopUFGb%5 z5iJW01Vd?=-Lm>@2dy#GXe!Tazb)lAsQ)4<#VI!g!wP!yh%3Pu`e>|tG*vjrZp6ME z;?Ej7CY+E2*;4Y##Me>y$4MacGvG8rE9Gx{?V_L+3*3Ud-$Or4PsMK3K;XjjsRV_ZY z+1?xW!mpj|b8T|?A3V?-4~aAZr|7x~F`-V}xpM>3=XSwmm*){NC|eyzpf93gPp#XZ zV5^6xuA?>2VzoVUH}-$qgTr?=V6AEj2ec~sogk}~>G@u081`BYtMDu0Fv^Kt9WDBV zmYkbIvOM~>RM;s%sexbD!CTTVM`_pk9t1@OWje4!5N$G2NDj3qtAX@??()0dw2%dr z^q&u<*2$iN5;gr4iIs{w*(XuaEe-ATRLe{p>?FgFU&cYY&~EyfLvptt^#57!&aN$X zcQeX_+}h@j>r|Y9!PmTl)>?5CEOb)x_}2zZ+_bI8Dtgack6&|1zb3@C`&9NrJLD^Xwl3-`^}rKZ#*lVK3dPeEK$Y>UIYQ_h zo_+>tLR6r?%yao<-?Rltk3cSc17*g5HFetW%#MMJbuSgR4t>2*khYYcsV?v={~d@5 zOq}oigOQ(%5N+DO#^%i^Rp_`Uf@+*>d0=;7WQe4nSXyXwBBTm?`>H~*SaVu~q#+V1 z($eBy0dmW>i}Q)youZqi@BKncgw<}W9;C+e8A(UYK9WVyDn8w5@NZyErWrS$J;049 z+`5+btsu?s-~dA)C|NBk&UHIww@;lY4xb!(ixfxzwZ!kM-Unrz;Flb?#^fPy+Y(v9 zz;e|q{#GO1@?}IA%67acF;4ttNmd@K_1sA}R0c@;jH89(1lQi9hSNi1R*7TRzw4P0m+k3>Z?Uu`lQ+D7nx1vRyZdtvrxIiJ-)fW;fkF@JvSnNn8t-4y=@z}@E5s0Xi zE&Ajtii^0yzjrD0!f07+g_>+ieAn~P(PGQ=RTOxs8_@7 z;*7^?+#83z0(~)F-r%lr)5UR81<{t;(J^#icGc1d6DN5VL~d2SQ%%T1#}Sd0v8{8D zWk9|2{+>M$bN^G+Ho#F#WodrzC~<=R{Bzh1s?7J;(23PCroMfa+9weNQl!2d5vY}i zFT5p!k=(L0`KVhzN~P~popyS|D9IlkZDbculJbqT-*y*R4vSPHC{+h~n9(#21vZe` z+fU@X?Su!#F!#_faRTJ!i-aqHcMy@~rDe!A0lZf%HK^m*`L3WakzXZQo=teaR4feF zp-F!{e-sfoHm_Z6p; z5{&78%)Wyi9z)Q)o3S&-cm*m&PX-|Tj9`A;v?Q}$W8$S8ug~uv#IBnNp)U%%YgnbgC%^O4bAUsRSikp!FL)n zVpIboh&z>DAx0@X>BUWL1;NCJ!+#;xIvIuj}V9v65hk(E1XD5?B%GJ7ylI)VMKAXoZJnm5Cu-OhV!8$ zr`6qM8QMcx^+#zKV?%C=9RTAd?*dN7Sc8?qGsJxX+m`Aogbi>#Zw^t%YYtN{CV>+_ z7eH-We#rUxaC>;nFuAT9c1PKU~=co&17(1G;;wh1)kcC1%_G~13BC+G{+nRn` znjgrPevRt>w@G}=o4lbhOwm%?akSnD`lukNjq_i!kogLX~Lgcy6 zsSY&>ob!Xi@K96-x16yu+oBkZ)n?u$)AL%BTbXNv0D}W`(zM+*gCuvs>p>;hGw>vg zi8>yDN57+m+p)e#ghI8U%_OUPuPM#K2f)}0L)vh_x!qD3Ze2ScKn z{HY@7ag-+5-ZJ$Nz^D;DC(-7P9G3Gq-TORnR!I~GnVo+%6k&uL?V$I%IJtX7Uv+Y& z^=y`>j+^+tDQX}iyBZ)X#JJ>t48}-y{SdC>5FK}*6$W%Z4WO%Jb- z*v$AZG2|Y7B_#$D;NHI#qk}*j+3di087(z*-Hqu3!C04G~x(}SCBsWO>>hLl8IoonEq+0-G zTP?(iVSoS_&+5(Q6v9%2ePavOM$$QuBCz;ixCGAR)m(;)3#3eQ8sWi0mR|!|irGBy z+QAvX4#~SlAm2rsl=&ZZ1|%D-Ad)+NvdAzy*3R1;r1Vy3J!pW@ygp(^td)^XHoz1` zCm<-9qrV<7zfUl+FUxaW_c){@{9U>A#007Q2_GTLF)bKi^x}}eH7S2sdtL;rDlwl$A5#~U%x1wa*{N|xbu-0zL&1i0~qa^RV! z70Bfv)FIhVi$tqGZkp(hEuSn;hk%*!{u*Kc$Oj<%{SK`hm&#$Uo}U`E4m^;E*mH+% zq7c|Gk&oRV22W)G18P<-NNYc=7DBc_Bt36b3>n%FMC#l?oLJZc{dDh-mFT@}&g7uF7Y;j5syb-nA8M7$Py-x<;a4bsD&ZbG*Opdza^Qi&tY2f{CuLmWk!4Dl$Uic8 z+r>PRriEOwI!5yO<=UMHDTR8|D3HKyF4)lS-WTc<1;^BL+k(RiQ|=vvlDC#3k)cD#3kp>BQLGy$rY=0gpf?+r^EB+TgA z*m+fq@zG0H7eGF~+nfEb_&~$|ziZzGzCyKWS7J;&$LKig%uk+QTfHsu(~^QI_+zhc zL^7zWGke4zw_PDwMefZla{{oZFCbR{s#RJ_9q7s$6?& z<1DV-u|6+eVkHL-H^4>sdTp%wh>Iw>dIk0or_{v_Ex#qt2yn!0+#e*kvABAIcZNj9 z#B;bQUw+39e%Vt=1uC1N+Hp_^ajTF+n;>>RH6{ODF<5Ppk(J z-0cs!WfC{5w3@BYVzp?BpY+fL(ZF%%Z$cb*ZcO@W0kR2Ml*wsz3qs zRq}?Wg%5bJ*!mY{8D>o<+Nuy9oay=^*`;ab4?gk#bgX!g6a~k`%B>r2`|GEqR-zs`_3yB?mY9(ZADaj3Hw4JK0EC->g$LlWi$%)23 z_E- zSAJ3mA|qjv5#)qn{qZbxhleZe6KH|)>)=Xq`Kb}tK^0$9nGQ?<4`ZGW$9E*g-3I-N zwl^x-AM8O@Jo0#;e=9EHbh9I_jE@N}r@xNhLQ_DSDP{Bbf?0qKM>BPOJ7Fig z2EGp|TwEdr&@2Gp7At+R?R)sckYGbbSX6$s4+LP7k8l8N)(oNzJfR`sHGN$IsFL&H zX^EiJYC08fmt*Y9F44(wq8a98QgHUR6KknL5R^E+jzG_Dk8UBbnDeYXnlEnEeZ-Y) z?P|)G4MmvfK7VyliJ{@;vN!(-4ZNC6yHN^T+F+<vTkz_HmH7c-T#y5*8z@X<=>ADqN6&RquR05h4 zmREm2lKjUpeCkQ~wXh0}7j%-GzE`Ol#F)=|ov|wdA8E&O#>{}0!SeAsfFpd5o?Jgw zo+<-0LbR?;HTXptuv~Jj+Duw-FBcm|Q_ou|dm+Je1ENPu2FAhc!NsIPPaX`ie#N^1~vYL z(RSY(Z)6i^^VX*SG?qYDfG5K`b47qHygpxl2=JBZklE$pM=D2YEe8j*WcSizTZg^a zX68Pk6&w&2mi#x-j04U@s_4;L)PnlelZ#8BV?=cfzs54yduGz+-sdxon>W}V8zvwk zVwhQ8i0)1+r{dM6xpltX3N2AAyBr{B*?nTgvv z10~Mp$=rd!*=50(ckfpV0d7Vk%&KC7>18{X&OSge(sVXy#8$ap8rQM#_Lw`Cl>3^} zK^jyiI-&KI=DN553DG91>E&X{Y%f_Qdwy97#>{DAIO9i!n<>-C%+<)XI^Y&b%wFkO zb4W`^EdEhS@O*^iwZ3`Qm&k^~vbF**9ii^dRm}tl9lHNRS2JV;+BkZ``@Bigx9}L$ za!Z1*yex#kyo9VF1jZ>@pKNLD*iY$(N^Fa9Ddd9!*<+&9yb#eF;eQaJr3j6vIUw37 zYB}`#&yIQ4pRxvII)`xSAB3(VWa@S!S4uKyu5JmW+v7XoI%nZYxNC z_C8PL4AKeQ zK}Yg0|Kq!CLWw9)iHXpHxD#;U2w%y+ndKk4$JYvM(;+hD>|?@#>X&|d!{h2hJl&le zPH%FwfcCWfd(WNbpFz5kd1Tyw5ImOyOQrmM_8C(i`;J9pdzSQzOf~2fcB+4$q9wB( zylkxA(Z%QSav*i)f8wR&%_crQB`MzeCwh>dztz+D$aic+5xwejcX(&Q%_=3vrB7{E=)oa>Fg(zmOV;LFo2G?R z(i`0AUZc`~UUujaE61v*JWDE<*fZnu>Z8cYDfkX_W_-xPw9;$a@xnA*Z6RTVLn!%V z$w$QYpbOgPrV$QlR2Ix=OKzpVqEyCaq2sC2h7F=URw1KOb3GxsgaE(YqKo@ULtkQz zyl42B{GSS2u=eWg&m;8fD!PjwM?hwvOam9@<^gmGv+C-DJ5!e^-(7|_o&}q$+fR0d z-+TA8UXC64Nr=`+nHKzZ2x#J|jA~Qs@U_5{&fCVDGBuVkM0u3@@S~6g>!i7oZr$_- zWccc8R}We0{e7Xc9v?(;unS8^5pQ;_?w@bbJ$)d!EkdOXA7fwuJle(TOVv^#whQ>Y z#x*t?^!8xmqW!g$?_dZ;YOGA4LIcQcr1gU0JYWu<*vSHc;3W|R^8DcQ*Zr$EQ=-)n z%gc(Zvog@}Sg~b64=K}RY<0+jhOXLoc;x`Zu=K9P=p0A`Eqj3r6wu4X@-us6nZ0(eEKmooD7>r7UzrN5db2AAfy_vI zEEX1HQvI5^)}qGUVX-8m7rc0HU;h=;Pu`F*!=1c2Xpi@P5LeI?7(|YJ7y`9nJ`-x6qgZ!z(~h*1c07 zEZM7%2uFseqj|5dozZ}3wtmT&wY;wGJtOz3PF{LL>bY@a67$P_c8JH9kcsCX?;yX& zGTP~j6Q_4ggPyj}O}p7%y3U#$_QE@G^q%~XWkwol4p%=bh9ozqF`ujYdVG+QbgbeZ z4BVzG6t`LL;R2hh%XY-jGzancNn)_Zyi3jJQxoG3$>xVD~Z%i z&fQZuLOTL&xA1{)jtihxR3ie+KghjxHGO4ar1aQtX=>~}JrK2@lPR=+%v5-ZL>FWm zDD{Y>oQTcC`AVIj5RI9dnzwhZO*YmD7rN0*4$RyTow<;SiKm88W@ro%db>G*o$WL zO2{&p&+KW`j#{XV`75ul;Sg=o%(3(;hKOwBW42~D6XGN()&n-QtgV##e6Tmdnl`VY zun<}7{^2mq9dkL?6uzQ^TVp4{qc7PEdtoP66WBY3<%A(%}%kwmQo5B7TX)G=WIk*LlreuR1<&ton)I4UOe8fjI-KxxkR z;rM=Dn5WxW*+YN58lww~LKc>H?|To%#|)+S9Z2QE*_#b4s|jZm5$4(9Vy;odP_V5$+@D7eLcnZ0f=`p%wBY6Dc%!U%xQvQYM4I z@0gn@QeeyoiOosj*l*wJ|B9S#Jo6_PPLSusnj;Se1hK+xk(G$iI;h{-EYG~CeUw)B zH6K-rDKJ8eG0RjKiow+JG5xU{hJ5gq{LdcY{M-nWjnlnH_wx_a7a#6(X}FBQi?804 z1FcMlsB-(;7`t)o#Emj3@H8mM6B@Ob8^OVc$^dB^Sv2?0^5V#8==%5UN!qc@m2NP1 z77&306#Av#v0pTL`vr+XoS&~LmSAt`S)BTox#A#o2t@* zWiWyQVD|924R|mpB?9~>Ku0D*%nrnE$w#N%T>;do{%=Ee2$0ZFTPxnY33+-MbTQO+ z$$Az`R^d1$UYqd-gXwMwj!Wi2Q&03w1F4Zzh2p1=!G~Olg z8iB#C1F-zpBJxDFJbP%4(AYustRgAy!Yn?;@T7lt#ZCZzQ$)Oe{D6$~ExrG^Gf!EY zWj&ZsQ~-X0)qZENWpVmHJ2NuCY^oO?jNbtp*8{$7D`nqPc?oK87O2q|eYHA#NU9wUhTjEXSvV({#g|Ll$<_AaW<6ak$>i=9^Cl>4Cuo zFlo9SI81Slq(NpX;Hn^wW_H?3ulSt>vyyv(<$6q97(p6f7^&;bP)^$)36D72N$yyo z%K1V=!)kek&1vnNlkPjFU9Fv9#Zg0x3lO)Vz;aW>CTQ*Mt^KZr zSI36Ksa`N?(h;EE-ee06-e%a)5AAB;5!b=>$Y`K;|KfJU=LX2+!CVQNFs&*!d7edb zh^o^k%YhIu6t)uL9f77|qp(SAY`Yw9wDC7*Xf`-h7A;TlNNLAIwvp%J(S)@wayD+j zj0E5fl?u~S2ltu*w#OBpY--MHZy-X3Tdm`;4ULMPYEOf>8onZB2aEiS44v!ZoH+oB z+lYdx;Z4w2am-t|+R@M9L@)nt=CTl83LCK+oUIt9&I{LrX5l8}ZY94{bWjrGsm9|5}og{flcdw44@<;&B){~ljIEHC+r4!Y?F`w`k?;5 o%fO#G{Hqek8UA;B$xjpc4om-<>xJM?c(}NZSlF3YB2HcTFG5G!*8l(j literal 0 HcmV?d00001 diff --git a/assets/nomad_500x500.png b/assets/nomad_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..4320dedd5a74beecd8fa8477f6ebf62d41cf81c3 GIT binary patch literal 12360 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 PtV4Ee^>8l09Etruac*$S literal 0 HcmV?d00001 diff --git a/assets/packer_500x500.png b/assets/packer_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..8c849dd659c17af7316ba4307497e554a82735ac GIT binary patch literal 12595 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 Kww2(IUHV@?f;jU4 literal 0 HcmV?d00001 diff --git a/assets/packer_white_500x500.png b/assets/packer_white_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..a70bd067a63d87dd1c27bb1944968fa81eaf678c GIT binary patch literal 12250 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 literal 0 HcmV?d00001 diff --git a/assets/terraform_500x500.png b/assets/terraform_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..4b11821fbfe2c946b183eb1ddedbc08d5dde054c GIT binary patch literal 14449 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 literal 0 HcmV?d00001 diff --git a/assets/terraform_white_500x500.png b/assets/terraform_white_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..adaa222983571abd0f3d31fed515531552ac5344 GIT binary patch literal 11943 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 O^I4nQ@4JWdJoi6T#$0s( literal 0 HcmV?d00001 diff --git a/assets/vagrant_500x500.png b/assets/vagrant_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..f0c33d40ffa388ee22502287d0c775924641cd92 GIT binary patch literal 15814 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 V-63fV4~IqUckZ^nYvq0Re*nA~>$d;^ literal 0 HcmV?d00001 diff --git a/assets/waypoint_500x500.png b/assets/waypoint_500x500.png new file mode 100755 index 0000000000000000000000000000000000000000..1501e8247bcf4b5f10a165878e9c5f4a9de7b8a2 GIT binary patch literal 13987 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 nr87)y*@3}z(8h)~wa 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 server{ - Vault Server - Consul Server - Nomad Server - }; -``` - -## Testing/Preprod deployment - -For testing, of pre-production deployments, running all services on the same nodes might be a good way to cut cost and/or save resources. - -## Production deployment - -For production use, it is recommended to separate concerns as much as possible. This means that consul, vault and nomad, as well as the haproxy services, should be on different nodes altogether. The **client-facing** and **cluster-facing** interfaces should also be separated. - -Ideally, you would need: - - an odd number (3 to 5) of consul servers - - an odd number (3 to 5) of vault servers - - an odd number (3 to 5) of nomad servers - - multiple (2 to 3) haproxy servers - -The **nomad**, **vault** and **consul** servers should have **two network interfaces**, and one of them should be reachable from the haproxy nodes. - -The architecture for this infrastructure would look like: - -```mermaid -graph TD - client[Client] -->|https :443| keepalived - keepalived[VIP] --> haproxy1[HAProxy] & haproxy2[HAProxy] - subgraph frontends - direction LR - haproxy1[HAProxy] - haproxy2[HAProxy] - end - - haproxy1[HAProxy] & haproxy2[HAProxy] -->|http :8500| consul - - subgraph consul - direction LR - consul1[Consul 01] <--> consul2[Consul 02] & consul3[Consul 03] & consul4[Consul 04] & consul5[Consul 05] - consul2[Consul 02] <--> consul3[Consul 03] & consul4[Consul 04] & consul5[Consul 05] - consul3[Consul 03] <--> consul4[Consul 04] & consul5[Consul 05] - consul4[Consul 04] <--> consul5[Consul 05] - - end - - haproxy1[HAProxy] & haproxy2[HAProxy] -->|http :8200| vault - - subgraph vault - direction LR - vault1[Vault 01] <--> vault2[Vault 02] - vault2[Vault 02] <--> vault3[Vault 03] - vault3[Vault 03] <--> vault1[Vault 01] - end - - vault -->|Service registration| consul - haproxy1[HAProxy] & haproxy2[HAProxy] -->|http :4646| nomad - - subgraph nomad - direction LR - nomad1[Nomad 01] <--> nomad2[Nomad 02] - nomad2[Nomad 02] <--> nomad3[Nomad 03] - nomad3[Nomad 03] <--> nomad1[Nomad 01] - end - - nomad -->|Service registration| consul -``` -> **Note**: you can substract the haproxy part if using an external load-balancing solution, like AWS ALB,or any other LB technology, for connecting to your platform. diff --git a/docs/consul_clusters.md b/docs/consul_clusters.md deleted file mode 100644 index 462dd91..0000000 --- a/docs/consul_clusters.md +++ /dev/null @@ -1,3 +0,0 @@ -# Deploying a Consul cluster - -This documentation explains each steps necessary to successfully deploy a Consul cluster using the ednz_cloud.hashistack ansible collection. diff --git a/docs/extra_configuration.md b/docs/extra_configuration.md deleted file mode 100644 index cff5628..0000000 --- a/docs/extra_configuration.md +++ /dev/null @@ -1 +0,0 @@ -# Adding extra configuration options diff --git a/docs/general.md b/docs/general.md deleted file mode 100644 index 11459ae..0000000 --- a/docs/general.md +++ /dev/null @@ -1,103 +0,0 @@ -# General documentation - -## Configuration directory - -### Main configuration directory - -Hashistack Ansible uses a configuration directory to store all the configuration files and other artifacts. - -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. - -### Sub configuration directories - -#### Group configuration directories - -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 `{{ 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. - -- **Example**: - - If `etc/hashistack/globals.yml` looks like: - - ```yaml - --- - enable_vault: "no" - enable_consul: "no" - enable_nomad: "no" - ``` - - And `etc/hashistack/nomad_servers/globals.yml` looks like: - - ```yaml - --- - enable_nomad: "yes" - ``` - - Servers in the `nomad_servers` group will end up with the following configuration: - - ```yaml - --- - enable_vault: "no" - enable_consul: "no" - enable_nomad: "yes" - ``` - -This approach lets you customize your deployment for your exact needs. - -#### Host configuration directories - -Additionally, within each `group configuration directory`, you can add `host configuration directory`, that will be named after the hosts defined in your `inventory`. These host directories can also be populated with a `globals.yml` file, that will superseed the `group` and `deployment` configuration files. - -- **Example** - - If `etc/hashistack/globals.yml` looks like: - - ```yaml - --- - enable_vault: "no" - enable_consul: "no" - enable_nomad: "no" - api_interface: "eth0" - ``` - - And `etc/hashistack/nomad_servers/globals.yml` looks like: - - ```yaml - --- - enable_nomad: "yes" - api_interface: "eth1" - ``` - - And `etc/hashistack/nomad_servers/nomad-master-01/globals.yml` looks like: - - ```yaml - api_interface: "eth0.vlan40" - ``` - - Servers in the `nomad_servers` group will end up with the following configuration: - - ```yaml - --- - enable_vault: "no" - enable_consul: "no" - enable_nomad: "yes" - api_interface: "eth1" - ``` - Except for host `nomad-master-01`, who will have the following: - - ```yaml - --- - enable_vault: "no" - enable_consul: "no" - enable_nomad: "yes" - api_interface: "eth0.vlan40" - ``` diff --git a/docs/haproxy_servers.md b/docs/haproxy_servers.md deleted file mode 100644 index 3bbda8f..0000000 --- a/docs/haproxy_servers.md +++ /dev/null @@ -1,104 +0,0 @@ -# Deploying HAProxy frontends - -This documentation explains each steps necessary to successfully deploy HAProxy frontends for your deployment, using the ednz_cloud.hashistack ansible collection. - -## Prerequisites - -You should, before attempting any deployment, have read through the [Quick Start Guide](./quick_start.md). These steps are necessary in order to ensure smooth operations going forward. - -## Variables - -### Basics - -First, in order to deploy the HAproxy frontends, you need to enable the deployment. - -```yaml -enable_haproxy: "yes" -``` - -You can also configure the version of haproxy you would like to use. This has very little impact, and should most likely be left untouched to whatever the collection version is defaulting to (which is the version that it is tested against). - -```yaml -haproxy_version: "2.8" -``` -This version can either be `latest`, or any `X`, `X.Y`, `X.Y.Z` tag of the [haproxytech/haproxy-debian](https://hub.docker.com/r/haproxytech/haproxy-debian) docker image. - -For production deployment, it is recommended to use the `X.Y.Z` syntax. - -The `deployment_method` variable will define how to install vault on the nodes. - -By default, it runs haproxy inside a docker container, but this can be changed to `host` to install haproxy from the package manager. - -Note that not all versions of haproxy are available as a package on all supported distributions. Please refer to the documentation of [ednz_cloud.deploy_haproxy](https://github.com/ednz-cloud/deploy_haproxy) for details about supported versions when installing from the package manager. - -```yaml -deployment_method: "docker" -``` - -### General settings - -There aren't many settings that you can configure to deploy the HAProxy frontends. First you'll need to configure a Virtual IP, and pass it in the `globals.yml` configuration file. - -```yaml -hashistack_external_vip_interface: "eth0" -hashistack_external_vip_addr: "192.168.121.100" -``` - -This is used to configure keepalived to automatically configure this VIP on one of the frontend, and handle failover. - -You also need to configure the names that will resolve to your different applications (consul, nomad, vault). These names should resolve to your Virtual IP, and will be used to handle host-based routing on haproxy. - -```yaml -consul_fqdn: consul.ednz.lab -vault_fqdn: vault.ednz.lab -nomad_fqdn: nomad.ednz.lab -``` - -With this configuration, querying `http://consul.ednz.lab` will give you the consul UI and API, through haproxy. - -> Note: subpaths are not yet supported, so you cannot set the fqdn to `generic.domain.tld/consul`. This feature will be added in a future release. - -### Enabling external TLS - -To enable external TLS for your APIs and UIs, you will need to set the following variable. - -```yaml -enable_tls_external: true -``` - -This will enable the https listener for haproxy and configure the http listener to be a https redirect only. - -## Managing external TLS certificates - -### Generating certificates with hashistack-ansible - -If you don't care about having trusted certificates (e.g. for developement or testing purposes), you can generate some self-signed certificates for your applications using the `generate_certs.yml` playbook. - -```bash -ansible-playbook -i multinode.ini ednz_cloud.hashistack.generate_certs.yml -``` - -This will generate self-signed certificates for each applications that has been enabled in your `globals.yml`, and for then respective fqdn (also configured in `globals.yml`). - -These certificates are going to be placed in `etc/hashistack/certificates/external/`, and will be named after each fqdn. These files should be encrypted using something like ansible-vault, as they are sensitive. - -### Managing your own TLS certificates - -Similarly, you can manage your own TLS certificates, signed by your own CA. Your certificates should be placed in the `etc/hashistack/certificates/external/` directory, similar to the self-signed ones, and be named like: - -`.pem` and `.pem.key`, for each application. - -At the moment, setting all certificates in a single file is not supported, but will be added in a later release. - -These certificates will be copied over to the `haproxy_servers` hosts, in `/var/lib/haproxy/certs/`. - - -### Managing certificates externally - -In case you already have systems in place to deploy and renew your certificates, you can simply enable the options in `globals.yml` to not manage certificates directly in hashistack-ansible. - -```yaml -external_tls_externally_managed_certs: true -``` - -Enabling this option will prevents the playbooks from trying to copy certificates over, but the HAProxy nodes will still expect them to be present. It is up to you to copy them over. diff --git a/docs/nomad_clusters.md b/docs/nomad_clusters.md deleted file mode 100644 index e1d7140..0000000 --- a/docs/nomad_clusters.md +++ /dev/null @@ -1,82 +0,0 @@ -# Deploying a Nomad cluster - -This documentation explains each steps necessary to successfully deploy a Nomad cluster using the ednz_cloud.hashistack ansible collection. - -## Prerequisites - -You should, before attempting any deployment, have read through the [Quick Start Guide](./quick_start.md). These steps are necessary in order to ensure smooth operations going forward. - -## Variables - -### Basics - -First, in order to deploy a nomad cluster, you need to enable it. - -```yaml -enable_nomad: "yes" -``` - -Selecting the nomad version to install is done with the `nomad_version` variable. - -```yaml -nomad_version: latest -``` - -The vault version can either be `latest` or `X.Y.Z`. - -For production deployment, it is recommended to use the `X.Y.Z` syntax. - -### General settings - -First, you can change some general settings for nomad, like the dc and region options. - -```yaml -nomad_datacenter: dc1 -nomad_region: global -``` - -### ACLs settings - -By default, ACLs are enabled on nomad, and automatically bootstrapped. -You can change this by editing the `nomad_acl_configuration` variable: - -```yaml -nomad_acl_configuration: - enabled: true - token_ttl: 30s - policy_ttl: 60s - role_ttl: 60s -``` - -### Consul integration settings - -By default, if consul if also enabled, nomad will use it to register itself as a consul service and also use consul to automatically join the cluster. - -```yaml -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: [] -``` - -Optionally, you can add tags to you nomad services, or disable the consul integration if you don't plan on using it. - -### Vault integration settings - -Vault integration for nomad is by default disabled, as it requires some vault configuration that is out of the scope of this collection. - -You can, once you have deployed and configured vault (or if you are using an external vault not managed by the collection), enable the integration - -```yaml -nomad_enable_vault_integration: false -nomad_vault_integration_configuration: {} -``` - -For configuration options, please refer to the [Official Documentation](https://developer.hashicorp.com/nomad/docs/configuration/vault) - -### Drivers settings - -### Internal TLS diff --git a/docs/quick_start.md b/docs/quick_start.md deleted file mode 100644 index c06db76..0000000 --- a/docs/quick_start.md +++ /dev/null @@ -1,154 +0,0 @@ -# Quick Start Guide - -This documentation will show you the preparation steps necessary to ensure that you environment is ready to deploy cluster(s). - -## Prerequisites - -### Recommended readings - -It’s beneficial to learn basics of both [Ansible](https://docs.ansible.com/) and [Docker](https://docs.docker.com/)(for docker deployments) before running Hashistack Ansible. - -### Operating - -The only supported operating systems currently are: -- Debian - - 11, Bullseye - - 12, Bookworm -- Ubuntu - - 20.04, Focal - - 22.04, Jammy - -Other Debian-based distributions might work, but **are not tested**, and may break at any given update. - -### Target Hosts - -Target hosts are the hosts you are planning on deploying cluster(s) to. - -These hosts must satisfy the following minimum requirements: -- Be reachable via ssh by the deployment node (the machine running the ansible playbooks), with a user that has the ability to escalate privileges. -- Be able to comunicate with each other, according to your cluster topology (vault hosts must all be able to reach each other, etc...) -- Be synced to a common time -- Have less than 10ms of latency to reach each other (raft consensus algorithm requirement) -- Be using systemd as their init system. - -Ideally, hosts are recommended to satisfy the following recommendations: -- Have 2 network interfaces: - - One that is public facing for client-to-server traffic - - One that is not public facing for server-to-server and deployment-to-server communications -- Have a minimum of 8GB of memory (less will work, but the larger the scale, the higher the RAM requirements will be) -- Have a minimum of 40GB of free disk space - -## Prepare the deployment host - -1. Install the virtual environment dependencies. - -```bash -sudo apt update -sudo apt install git python3-dev libffi-dev gcc libssl-dev python3-venv -``` - -2. Create a python virtual environment and activate it. - -```bash -python3 -m venv /path/to/venv -source /path/to/venv/bin/activate -``` - -3. Ensure the latest version of pip is installed - -```bash -pip install -U pip -``` - -4. Install [Ansible](http://www.ansible.com/). Hashistack-Ansible requires at least Ansible **7**(or ansible-core **2.15**) - -```bash -pip install 'ansible-core>=2.15' -``` - -5. Create the directory structure. This is not required but **heavily** recommended. - -```bash -mkdir -p etc/hashistack collections inventory roles -touch ansible.cfg -``` - -Your directory structure should look like this - -```bash -. -β”œβ”€β”€ ansible.cfg -β”œβ”€β”€ collections -β”œβ”€β”€ etc -β”‚Β Β  └── hashistack -β”œβ”€β”€ inventory -└── roles -``` - -6. Edit the `ansible.cfg` file with the minimum requirements. - -```bash -[defaults] -roles_path = ./roles/ -collections_path = ./collections/ -inventory = ./inventory/ -``` - -7. Install the `ednz_cloud.hashistack` ansible collection - -```bash -ansible-galaxy collection install ednz_cloud.hashistack:== -``` - -You should now have a directory under `./collections/ansible_collections/ednz_cloud/hashistack` - -8. Install the other dependencies required by `ednz_cloud.hashistack` - -```bash -ansible-galaxy install -r ./collections/ansible_collections/ednz_cloud/hashistack/roles/requirements.yml -``` - -This will install roles that are not packaged with the collection, but are still required in order to run the playbooks. - -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. - -This can be done by running the `generate_credentials.yml` playbook. - -```bash -ansible-playbook -i inventory/inventory.ini ednz_cloud.hashistack.generate_credentials.yml -``` - -This will create and populate `etc/hashistack/secrets/credentials.yml` - -> [!WARNING] -> This file is VERY SENSITIVE, as it holds the root tokens and other credentials for consul and nomad clusters. - -This does not generate vault credentials, as it is not possible to generate those in advance. These credentials will be generated, if you enable the vault deployment, during the bootstrap process of the vault cluster, and stored in `etc/hashistack/secrets/vault.yml` - -> [!WARNING] -> It is HIGHLY recommended to encrypt these two files before enventually commiting them to source control. You can do so using tools like [ansible-vault](https://docs.ansible.com/ansible/latest/cli/ansible-vault.html) or [sops](https://github.com/getsops/sops). - -## Running preflight checks and bootstrap playbooks - -Before running the main deployment playbook, you might want to run the `bootstrap` and `preflight` playbooks, which do a number of checks to ensure all hosts are setup correctly for deployment. - -```bash -ansible-playbook -i inventory/inventory.ini ednz_cloud.hashistack.bootstrap.yml -ansible-playbook -i inventory/inventory.ini ednz_cloud.hashistack.preflight.yml -``` - -These playbooks will run a number of checks, and installations, in order to ensure the target hosts, as well as your deployment environment are correctly setup in order to install all the components. diff --git a/docs/tls_guide.md b/docs/tls_guide.md deleted file mode 100644 index 2eb4699..0000000 --- a/docs/tls_guide.md +++ /dev/null @@ -1,6 +0,0 @@ -# TLS Guide - - -create certificate/ca directory - -`ansible-playbook -i inventory/multinode.ini ednz_cloud.hashistack.generate_certs.yml` diff --git a/docs/vault_clusters.md b/docs/vault_clusters.md deleted file mode 100644 index 96ed1cc..0000000 --- a/docs/vault_clusters.md +++ /dev/null @@ -1,107 +0,0 @@ -# Deploying a Vault cluster - -This documentation explains each steps necessary to successfully deploy a Vault cluster using the ednz_cloud.hashistack ansible collection. - -## Prerequisites - -You should, before attempting any deployment, have read through the [Quick Start Guide](./quick_start.md). These steps are necessary in order to ensure smooth operations going forward. - -## Variables - -### Basics - -First, in order to deploy a Vault cluster, you need to enable it. - -```yaml -enable_vault: "yes" -``` - -Selecting the vault version to install is done with the `vault_version` variable. - -```yaml -vault_version: latest -``` - -The vault version can either be `latest` or `X.Y.Z`. - -For production deployment, it is recommended to use the `X.Y.Z` syntax. - -### General settings - -First, you can change some general settings for vault. - -```yaml -vault_cluster_name: vault -vault_enable_ui: true -vault_seal_configuration: - key_shares: 3 - key_threshold: 2 -``` - -### Storage settings - -The storage configuration for vault can be edited as well. By default, vault will be configured to setup `raft` storage between all declared vault servers (in the `vault_servers` group). - -```yaml -vault_storage_configuration: - raft: - path: "{{ hashicorp_vault_data_dir }}/data" - node_id: "{{ ansible_hostname }}" - retry_join: | - [ - {% for host in groups['vault_servers'] %} - { - 'leader_api_addr': 'http://{{ hostvars[host].api_interface_address }}:8200' - }{% if not loop.last %},{% endif %} - {% endfor %} - ] -``` - -While this is the [recommended](https://developer.hashicorp.com/vault/docs/configuration/storage#integrated-storage-vs-external-storage) way to configure storage for vault, you can edit this variable to enable any storage you want. Refer to the [vault documentation](https://developer.hashicorp.com/vault/docs/configuration/storage) for compatibility and syntax details about this variable. - -Example: - -```yaml -# MySQL storage configuration -vault_storage_configuration: - mysql: - address: "10.1.10.10:3006" - username: "vault" - password: "vault" - database: "vault" -``` - -### Listener settings - -#### TCP listeners - -By default, TLS is **disabled** for vault. This goes against the Hashicorp recommendations on the matter, but there is no simple way to force the use of TLS (yet), without adding a lot of complexity to the deployment. - -The listener configuration settings can be modified in `vault_listener_configuration` variable. - -```yaml -vault_listener_configuration: - tcp: - address: "0.0.0.0:8200" - tls_disable: true -``` -By default, vault will listen on all interfaces, on port 8200. you can change it by modifying the `tcp.address` property, and adding you own listener preferences. - -#### Enabling TLS for Vault - -In order to enable TLS for Vault, you simply need to set the `vault_enable_tls` variable to `true`. - -At the moment, hashistack-Ansible does nothing to help you generate the certificates and renew them. All it does is look inside the `etc/hashistack/vault_servers/tls` directory on the deployment node, and copy the files to the destination hosts in `/etc/vault.d/config/tls`. The listener expect **2 files** by default, a `cert.pem`, and a `key.pem` file. - -Please refer to the [vault documentation](https://developer.hashicorp.com/vault/docs/configuration/listener/tcp) for details bout enabling TLS on vault listeners. - -In case you want to add more configuration to the vault listeners, you can add it to the `vault_extra_listener_configuration` variable, which by default is empty. This variable will be merge with the rest ofthe listener configuration variables, and takes precedence over all the others. - -> **Waring** -> At the moment, hashistack-ansible does not support setting up multiple TCP listeners. Only one can be set. - -### Plugins for Vault - -To enable plugin support for Vault, you can set the `vault_enable_plugins` variable to true. This variable will add the necessary configuration options in the vault.json file to enable support. Once enabled, you can simply place your compiled plugin files into the `etc/hashistack/vault_servers/plugin` directory. They will be copied over to the `/etc/vault.d/config/plugin` directory on the target nodes. - -Refer to the [vault documentation](https://developer.hashicorp.com/vault/docs/plugins/plugin-management) for details about enabling and using plugins. diff --git a/galaxy.yml b/galaxy.yml index d979ddd..f89e03c 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -21,7 +21,8 @@ issues: http://example.com/issue/tracker # artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This # uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', # and '.git' are always filtered. Mutually exclusive with 'manifest' -build_ignore: [] +build_ignore: + - assets/* # A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a # list of MANIFEST.in style # L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key diff --git a/molecule/no_tls_multi_node/prepare.yml b/molecule/no_tls_multi_node/prepare.yml index 9973f22..98b7baf 100644 --- a/molecule/no_tls_multi_node/prepare.yml +++ b/molecule/no_tls_multi_node/prepare.yml @@ -1,6 +1,6 @@ --- - name: Include certificate generation playbook - ansible.builtin.import_playbook: ednz_cloud.hashistack.generate_certs.yml + ansible.builtin.import_playbook: ednz_cloud.hashistack.certificates.yml - name: Include bootstrap playbook ansible.builtin.import_playbook: ednz_cloud.hashistack.bootstrap.yml diff --git a/molecule/tls_multi_node/prepare.yml b/molecule/tls_multi_node/prepare.yml index 9973f22..98b7baf 100644 --- a/molecule/tls_multi_node/prepare.yml +++ b/molecule/tls_multi_node/prepare.yml @@ -1,6 +1,6 @@ --- - name: Include certificate generation playbook - ansible.builtin.import_playbook: ednz_cloud.hashistack.generate_certs.yml + ansible.builtin.import_playbook: ednz_cloud.hashistack.certificates.yml - name: Include bootstrap playbook ansible.builtin.import_playbook: ednz_cloud.hashistack.bootstrap.yml diff --git a/playbooks/deploy.yml b/playbooks/deploy.yml index 356525d..e183281 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -56,18 +56,3 @@ apply: tags: - nomad - - - # - 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 deleted file mode 100644 index 5da90dc..0000000 --- a/playbooks/generate_certs.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -# hashistack generate certificates playbook -- name: "Generate certificates" - hosts: all, !deployment - strategy: linear - gather_facts: true - become: true - tasks: - - name: "Import variables" - ansible.builtin.include_role: - name: ednz_cloud.hashistack.hashistack - tags: - - always - - - name: "Create Certificate Authority" - ansible.builtin.include_role: - name: ednz_cloud.hashistack.hashistack_ca - apply: - delegate_to: localhost - tags: - - always diff --git a/playbooks/generate_credentials.yml b/playbooks/generate_credentials.yml deleted file mode 100644 index a705d11..0000000 --- a/playbooks/generate_credentials.yml +++ /dev/null @@ -1,85 +0,0 @@ ---- -# hashistack generate certificates playbook -- name: "Generate credentials" - hosts: deployment - strategy: linear - gather_facts: true - become: true - tasks: - - name: "Generate consul credentials" - block: - - name: "Generate consul gossip encryption key" - 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: - _consul_root_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - - - name: "Generate consul agents credentials" - ansible.builtin.set_fact: - _consul_agents_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - _consul_agents_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - - - name: "Generate consul vault credentials" - ansible.builtin.set_fact: - _consul_vault_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - _consul_vault_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - - - name: "Generate consul nomad server credentials" - ansible.builtin.set_fact: - _consul_nomad_server_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - _consul_nomad_server_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - - - name: "Generate consul nomad client credentials" - ansible.builtin.set_fact: - _consul_nomad_client_accessor: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - _consul_nomad_client_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - - - name: "Generate nomad credentials" - block: - - name: "Generate nomad gossip encryption key" - 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: - _nomad_root_token: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | to_uuid }}" - - - name: "Ensure secrets directory is created" - ansible.builtin.file: - path: "{{ hashistack_sub_configuration_directories['secrets'] }}" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: '0755' - - - name: "Write credentials file" - ansible.builtin.template: - src: templates/credentials.yml.j2 - 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 0303894..c03e234 100644 --- a/playbooks/group_vars/all/all.yml +++ b/playbooks/group_vars/all/all.yml @@ -42,3 +42,36 @@ consul_required_ports: [8300, 8301, 8302, 8500, 8501, 8502, 8503, 8600] nomad_required_ports: [4646, 4647, 4648] target: all, !deployment + +############################################################# +# consul -- DO NOT TWEAK UNLESS YOU KNOW WHAT YOU ARE DOING # +############################################################# + +consul_init_server: "{{ (inventory_hostname == groups['consul_servers'][0]) | bool }}" + +consul_api_addr: "{{ consul_api_scheme }}://{{ api_interface_address }}:{{ consul_api_port[consul_api_scheme] }}" +consul_api_scheme: "{{ 'https' if consul_enable_tls else 'http' }}" +consul_api_port: + http: 8500 + https: 8501 +consul_grpc_port: + http: 8502 + https: 8503 + +############################################################ +# nomad -- DO NOT TWEAK UNLESS YOU KNOW WHAT YOU ARE DOING # +############################################################ + +nomad_init_server: "{{ (inventory_hostname == groups['nomad_servers'][0]) | bool }}" + +nomad_api_addr: "{{ nomad_api_scheme }}://{{ api_interface_address }}:{{ nomad_api_port[nomad_api_scheme] }}" +nomad_api_scheme: "{{ 'https' if nomad_enable_tls else 'http' }}" +nomad_api_port: + http: "{{ nomad_address_configuration.ports.http }}" + https: "{{ nomad_address_configuration.ports.http }}" + +############################################################ +# vault -- DO NOT TWEAK UNLESS YOU KNOW WHAT YOU ARE DOING # +############################################################ + +vault_init_server: "{{ (inventory_hostname == groups['vault_servers'][0]) | bool }}" diff --git a/playbooks/group_vars/all/consul.yml b/playbooks/group_vars/all/consul.yml index 918204c..f58de29 100644 --- a/playbooks/group_vars/all/consul.yml +++ b/playbooks/group_vars/all/consul.yml @@ -1,19 +1,4 @@ --- -consul_init_server: "{{ (inventory_hostname == groups['consul_servers'][0]) | bool }}" - -##################### -# consul api config # -##################### - -consul_api_addr: "{{ consul_api_scheme }}://{{ api_interface_address }}:{{ consul_api_port[consul_api_scheme] }}" -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 # ########## diff --git a/playbooks/group_vars/all/globals.yml b/playbooks/group_vars/all/globals.yml index 359a20b..0e25e9c 100644 --- a/playbooks/group_vars/all/globals.yml +++ b/playbooks/group_vars/all/globals.yml @@ -3,7 +3,6 @@ # General options # ################### -enable_ingress: "yes" enable_vault: "yes" enable_consul: "yes" enable_nomad: "yes" @@ -16,11 +15,6 @@ 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 }}" - api_interface: "eth0" api_interface_address: "{{ ansible_facts[api_interface]['ipv4']['address'] }}" diff --git a/playbooks/group_vars/all/nomad.yml b/playbooks/group_vars/all/nomad.yml index 0203484..376fc0c 100644 --- a/playbooks/group_vars/all/nomad.yml +++ b/playbooks/group_vars/all/nomad.yml @@ -1,16 +1,4 @@ --- -nomad_init_server: "{{ (inventory_hostname == groups['nomad_servers'][0]) | bool }}" - -#################### -# nomad api config # -#################### - -nomad_api_addr: "{{ nomad_api_scheme }}://{{ api_interface_address }}:{{ nomad_api_port[nomad_api_scheme] }}" -nomad_api_scheme: "{{ 'https' if nomad_enable_tls else 'http' }}" -nomad_api_port: - http: "{{ nomad_address_configuration.ports.http }}" - https: "{{ nomad_address_configuration.ports.http }}" - ######### # Nomad # ######### @@ -101,7 +89,7 @@ nomad_client_configuration: bridge_network_subnet: "172.26.64.0/20" node_pool: >- {{ - 'ingress' if 'haproxy_servers' in group_names else + 'ingress' if 'nomad_ingress' in group_names else 'controller' if 'nomad_servers' in group_names else omit }} diff --git a/playbooks/group_vars/all/vault.yml b/playbooks/group_vars/all/vault.yml index f447156..c5a0f17 100644 --- a/playbooks/group_vars/all/vault.yml +++ b/playbooks/group_vars/all/vault.yml @@ -1,6 +1,4 @@ --- -vault_init_server: "{{ (inventory_hostname == groups['vault_servers'][0]) | bool }}" - ######### # Vault # ######### diff --git a/playbooks/inventory/multinode.ini b/playbooks/inventory/multinode.ini index bf01735..07cb778 100644 --- a/playbooks/inventory/multinode.ini +++ b/playbooks/inventory/multinode.ini @@ -1,7 +1,3 @@ -[haproxy_servers] -haproxy01 -haproxy02 - [vault_servers] vault01 vault02 @@ -25,7 +21,6 @@ nomad-client03 [consul_agents] [consul_agents:children] -haproxy_servers vault_servers nomad_servers nomad_clients @@ -45,7 +40,6 @@ nomad_clients vault_servers [common:children] -haproxy_servers vault_servers consul_servers nomad_servers