From 29d906bcd239f640c59ba39e8709c15be8168a63 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Fri, 29 Dec 2023 12:51:00 +0100 Subject: [PATCH] feat(modules): added unseal module --- .../no_tls_multi_node/converge.yml | 0 .../no_tls_multi_node/molecule.yml | 0 .../no_tls_multi_node/prepare.yml | 0 .../no_tls_multi_node/requirements.yml | 0 .../no_tls_multi_node/verify.yml | 0 .../no_tls_single_node/converge.yml | 0 .../no_tls_single_node/molecule.yml | 0 .../no_tls_single_node/prepare.yml | 0 .../no_tls_single_node/requirements.yml | 0 .../no_tls_single_node/verify.yml | 0 playbooks/deploy.yml | 31 +++++++-- plugins/modules/vault_init.py | 2 +- plugins/modules/vault_unseal.py | 66 ++++++++----------- 13 files changed, 55 insertions(+), 44 deletions(-) rename {extensions/molecule => molecule}/no_tls_multi_node/converge.yml (100%) rename {extensions/molecule => molecule}/no_tls_multi_node/molecule.yml (100%) rename {extensions/molecule => molecule}/no_tls_multi_node/prepare.yml (100%) rename {extensions/molecule => molecule}/no_tls_multi_node/requirements.yml (100%) rename {extensions/molecule => molecule}/no_tls_multi_node/verify.yml (100%) rename {extensions/molecule => molecule}/no_tls_single_node/converge.yml (100%) rename {extensions/molecule => molecule}/no_tls_single_node/molecule.yml (100%) rename {extensions/molecule => molecule}/no_tls_single_node/prepare.yml (100%) rename {extensions/molecule => molecule}/no_tls_single_node/requirements.yml (100%) rename {extensions/molecule => molecule}/no_tls_single_node/verify.yml (100%) diff --git a/extensions/molecule/no_tls_multi_node/converge.yml b/molecule/no_tls_multi_node/converge.yml similarity index 100% rename from extensions/molecule/no_tls_multi_node/converge.yml rename to molecule/no_tls_multi_node/converge.yml diff --git a/extensions/molecule/no_tls_multi_node/molecule.yml b/molecule/no_tls_multi_node/molecule.yml similarity index 100% rename from extensions/molecule/no_tls_multi_node/molecule.yml rename to molecule/no_tls_multi_node/molecule.yml diff --git a/extensions/molecule/no_tls_multi_node/prepare.yml b/molecule/no_tls_multi_node/prepare.yml similarity index 100% rename from extensions/molecule/no_tls_multi_node/prepare.yml rename to molecule/no_tls_multi_node/prepare.yml diff --git a/extensions/molecule/no_tls_multi_node/requirements.yml b/molecule/no_tls_multi_node/requirements.yml similarity index 100% rename from extensions/molecule/no_tls_multi_node/requirements.yml rename to molecule/no_tls_multi_node/requirements.yml diff --git a/extensions/molecule/no_tls_multi_node/verify.yml b/molecule/no_tls_multi_node/verify.yml similarity index 100% rename from extensions/molecule/no_tls_multi_node/verify.yml rename to molecule/no_tls_multi_node/verify.yml diff --git a/extensions/molecule/no_tls_single_node/converge.yml b/molecule/no_tls_single_node/converge.yml similarity index 100% rename from extensions/molecule/no_tls_single_node/converge.yml rename to molecule/no_tls_single_node/converge.yml diff --git a/extensions/molecule/no_tls_single_node/molecule.yml b/molecule/no_tls_single_node/molecule.yml similarity index 100% rename from extensions/molecule/no_tls_single_node/molecule.yml rename to molecule/no_tls_single_node/molecule.yml diff --git a/extensions/molecule/no_tls_single_node/prepare.yml b/molecule/no_tls_single_node/prepare.yml similarity index 100% rename from extensions/molecule/no_tls_single_node/prepare.yml rename to molecule/no_tls_single_node/prepare.yml diff --git a/extensions/molecule/no_tls_single_node/requirements.yml b/molecule/no_tls_single_node/requirements.yml similarity index 100% rename from extensions/molecule/no_tls_single_node/requirements.yml rename to molecule/no_tls_single_node/requirements.yml diff --git a/extensions/molecule/no_tls_single_node/verify.yml b/molecule/no_tls_single_node/verify.yml similarity index 100% rename from extensions/molecule/no_tls_single_node/verify.yml rename to molecule/no_tls_single_node/verify.yml diff --git a/playbooks/deploy.yml b/playbooks/deploy.yml index 718d3b4..6c64222 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -7,7 +7,7 @@ tasks: - name: "Include ednxzu.hashicorp_vault" ansible.builtin.include_role: - name: ednxzu.hashicorp_vault + name: ednxzu.hashistack.hashicorp_vault - name: "Initialize vault cluster" ednxzu.hashistack.vault_init: @@ -20,14 +20,33 @@ - name: "Write vault configuration to file" ansible.builtin.copy: - content: "{{ _vault_init_secret.state }}" + content: "{{ _vault_init_secret.state | to_nice_yaml}}" dest: "{{ configuration_directory }}/vault/vault_config" - mode: '0600' - when: _vault_init_secret.state + mode: '0644' + when: _vault_init_secret.changed run_once: true delegate_to: localhost + - name: "Load vault cluster variables necessary for unseal operation" + ansible.builtin.include_vars: + file: "{{ configuration_directory }}/vault/vault_config" + name: _vault_cluster_config + + - name: "Unseal the bootstrap node" + ednxzu.hashistack.vault_unseal: + api_url: "http://127.0.0.1:8200" + key_shares: "{{ _vault_cluster_config['keys'] }}" + max_retries: "{{ (_vault_cluster_config['keys'] | length) - 1 }}" + run_once: true + delegate_to: "{{ groups['vault_servers'] | first }}" + when: _vault_init_secret.changed + + - name: "Unseal all vault nodes" + ednxzu.hashistack.vault_unseal: + api_url: "http://127.0.0.1:8200" + key_shares: "{{ _vault_cluster_config['keys'] }}" + max_retries: "{{ (_vault_cluster_config['keys'] | length) - 1 }}" + - name: "Debug" ansible.builtin.debug: - msg: "{{ _vault_init_secret }}" - when: _vault_init_secret is defined \ No newline at end of file + msg: "{{ _vault_cluster_config }}" diff --git a/plugins/modules/vault_init.py b/plugins/modules/vault_init.py index 66fd63b..fb1c432 100644 --- a/plugins/modules/vault_init.py +++ b/plugins/modules/vault_init.py @@ -99,7 +99,7 @@ def run_module(): if not HAS_HVAC: module.fail_json( - msg="missing_required_lib(hvac)", + msg="Missing required library: hvac", exception=HVAC_IMPORT_ERROR ) diff --git a/plugins/modules/vault_unseal.py b/plugins/modules/vault_unseal.py index 7a623a4..0d72a4c 100644 --- a/plugins/modules/vault_unseal.py +++ b/plugins/modules/vault_unseal.py @@ -68,6 +68,7 @@ message: ''' from ansible.module_utils.basic import AnsibleModule +import traceback try: import hvac @@ -79,70 +80,61 @@ else: HAS_HVAC = True def run_module(): - # define available arguments/parameters a user can pass to the module module_args = dict( api_url=dict(type='str', required=True), - - name=dict(type='str', required=True), - new=dict(type='bool', required=False, default=False) + key_shares=dict(type='list', required=False, default=[]), + max_retries=dict(type='int', required=False, default=3) ) - # seed the result dict in the object - # we primarily care about changed and state - # changed is if this module effectively modified the target - # state will include any data that you want your module to pass back - # for consumption, for example, in a subsequent task result = dict( changed=False, original_message='', - message='' + state='' ) - # the AnsibleModule object will be our abstraction working with Ansible - # this includes instantiation, a couple of common attr would be the - # args/params passed to the execution, as well as if the module - # supports check mode module = AnsibleModule( argument_spec=module_args, supports_check_mode=True ) - if not HAS_HVAC: + # Check if hvac module is available + try: + import hvac + except ImportError: module.fail_json( - msg=missing_required_lib("hvac"), - exception=HVAC_IMPORT_ERROR, + msg="Missing required library: hvac", + exception=HVAC_IMPORT_ERROR ) - # if the user is working with this module in only check mode we do not - # want to make any changes to the environment, just return the current - # state with no modifications if module.check_mode: module.exit_json(**result) - # manipulate or modify the state as needed (this is going to be the - # part where your module will do what it needs to do) - result['original_message'] = module.params['name'] - result['message'] = 'goodbye' + # Initialize HashiCorp Vault client + client = hvac.Client( + url=module.params['api_url'] + ) - # use whatever logic you need to determine whether or not this module - # made any modifications to your target - if module.params['new']: - result['changed'] = True + # Unseal Vault + try: + retries = 0 + while client.sys.is_sealed() and retries < module.params['max_retries']: + key_share = module.params['key_shares'][min(retries, len(module.params['key_shares']) - 1)] + vault_unseal_result = client.sys.submit_unseal_key(key_share) + retries += 1 + except hvac.exceptions.VaultError as ve: + module.fail_json(msg=f"Vault unsealing failed: {ve}") - # during the execution of the module, if there is an exception or a - # conditional state that effectively causes a failure, run - # AnsibleModule.fail_json() to pass in the message and the result - if module.params['name'] == 'fail me': - module.fail_json(msg='You requested this to fail', **result) + # Check if the Vault is successfully unsealed + if client.sys.is_sealed(): + module.fail_json(msg="Vault unsealing failed: Maximum retries reached.") + + result['state'] = vault_unseal_result + result['changed'] = True - # in the event of a successful module execution, you will want to - # simple AnsibleModule.exit_json(), passing the key/value results module.exit_json(**result) - def main(): run_module() - if __name__ == '__main__': main()