From 08bd6815bdf58e384b29c38d4a256439a36f3571 Mon Sep 17 00:00:00 2001
From: Scott Solkhon <scottsolkhon@gmail.com>
Date: Fri, 2 Sep 2022 15:45:18 +0100
Subject: [PATCH] Add Hashi Vault support for Kolla passwords

This commit adds the necessary changes needed to support
reading and writing Kolla passwords to a Hashicorp Vault server
using Kolla-Ansible commands `kolla-readpwd` and `kolla-writepwd`.

This follows the support that was added into Kolla-Ansible in
the Change-Id Icf0eaf7544fcbdf7b83f697cc711446f47118a4d.

Change-Id: I732988e6160cc64d663d6ef8179f04d3e1226537
---
 ansible/inventory/group_vars/all/kolla        | 10 ++
 ansible/roles/kolla-ansible/defaults/main.yml | 10 ++
 .../kolla-ansible/library/kolla_passwords.py  | 93 +++++++++++++++++++
 ansible/roles/kolla-ansible/tasks/config.yml  |  8 ++
 etc/kayobe/kolla.yml                          | 13 +++
 ...ault-kolla-passwords-c4584aeeecd36e80.yaml |  5 +
 requirements.txt                              |  1 +
 7 files changed, 140 insertions(+)
 create mode 100644 releasenotes/notes/hashi-vault-kolla-passwords-c4584aeeecd36e80.yaml

diff --git a/ansible/inventory/group_vars/all/kolla b/ansible/inventory/group_vars/all/kolla
index daf4fa79..8df52540 100644
--- a/ansible/inventory/group_vars/all/kolla
+++ b/ansible/inventory/group_vars/all/kolla
@@ -456,6 +456,16 @@ kolla_ansible_target_venv: "{{ virtualenv_path ~ '/kolla-ansible' }}"
 # Password to use to encrypt the kolla-ansible passwords.yml file.
 kolla_ansible_vault_password: "{{ lookup('env', 'KAYOBE_VAULT_PASSWORD') | default }}"
 
+# Hashi Vault
+kolla_ansible_vault_addr: "{{ lookup('env', 'KAYOBE_VAULT_ADDR') | default }}"
+kolla_ansible_vault_mount_point: "{{ lookup('env', 'KAYOBE_VAULT_MOUNT_POINT') | default }}"
+kolla_ansible_vault_kv_path: "{{ lookup('env', 'KAYOBE_VAULT_KV_PATH') | default }}"
+kolla_ansible_vault_namespace: "{{ lookup('env', 'KAYOBE_VAULT_NAMESPACE') | default }}"
+kolla_ansible_vault_role_id: "{{ lookup('env', 'KAYOBE_VAULT_ROLE_ID') | default }}"
+kolla_ansible_vault_secret_id: "{{ lookup('env', 'KAYOBE_VAULT_SECRET_ID') | default }}"
+kolla_ansible_vault_token: "{{ lookup('env', 'KAYOBE_VAULT_TOKEN') | default }}"
+kolla_ansible_vault_cacert: "{{ lookup('env', 'KAYOBE_VAULT_CACERT') | default }}"
+
 # Whether TLS is enabled for the external API endpoints.
 kolla_enable_tls_external: "{{ kolla_enable_tls_internal if public_net_name == internal_net_name else 'no' }}"
 
diff --git a/ansible/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml
index 3cee9c96..82458f77 100644
--- a/ansible/roles/kolla-ansible/defaults/main.yml
+++ b/ansible/roles/kolla-ansible/defaults/main.yml
@@ -43,6 +43,16 @@ kolla_ansible_install_epel: false
 # Password to use to encrypt the passwords.yml file.
 kolla_ansible_vault_password:
 
+# Hashi Vault
+kolla_ansible_vault_addr:
+kolla_ansible_vault_mount_point:
+kolla_ansible_vault_kv_path:
+kolla_ansible_vault_namespace:
+kolla_ansible_vault_role_id:
+kolla_ansible_vault_secret_id:
+kolla_ansible_vault_token:
+kolla_ansible_vault_cacert:
+
 # Directory where Kolla config files will be installed.
 kolla_config_path:
 
diff --git a/ansible/roles/kolla-ansible/library/kolla_passwords.py b/ansible/roles/kolla-ansible/library/kolla_passwords.py
index 049f2b09..4c728f75 100644
--- a/ansible/roles/kolla-ansible/library/kolla_passwords.py
+++ b/ansible/roles/kolla-ansible/library/kolla_passwords.py
@@ -51,6 +51,49 @@ def kolla_mergepwd(module, old_path, new_path, final_path):
     module.run_command(cmd, check_rc=True,
                        path_prefix=virtualenv_path_prefix(module))
 
+def kolla_readpwd(module, file_path, vault_addr="", vault_mount_point="", vault_kv_path="",
+                  vault_namespace="", vault_role_id=None, vault_secret_id=None,
+                  vault_token=None, vault_cacert=""):
+    """Run the kolla-readpwd command."""
+
+    if vault_role_id and vault_secret_id:
+        vault_auth = ["--vault-role-id", vault_role_id,
+                      "--vault-secret-id", vault_secret_id]
+    else:
+        vault_auth = ["--vault-token", vault_token]
+
+    cmd = ["kolla-readpwd",
+           "--passwords", file_path,
+           "--vault-addr", vault_addr,
+           "--vault-mount-point", vault_mount_point,
+           "--vault-kv-path", vault_kv_path,
+           "--vault-namespace", vault_namespace,
+           "--vault-cacert", vault_cacert] + vault_auth
+
+    module.run_command(cmd, check_rc=True,
+                       path_prefix=virtualenv_path_prefix(module))
+
+def kolla_writepwd(module, file_path, vault_addr="", vault_mount_point="", vault_kv_path="",
+                  vault_namespace="", vault_role_id=None, vault_secret_id=None,
+                  vault_token=None, vault_cacert=""):
+    """Run the kolla-writepwd command."""
+
+    if vault_role_id and vault_secret_id:
+        vault_auth = ["--vault-role-id", vault_role_id,
+                      "--vault-secret-id", vault_secret_id, ]
+    else:
+        vault_auth = ["--vault-token", vault_token, ]
+    cmd = ["kolla-writepwd",
+           "--passwords", file_path,
+           "--vault-addr", vault_addr,
+           "--vault-mount-point", vault_mount_point,
+           "--vault-kv-path", vault_kv_path,
+           "--vault-namespace", vault_namespace,
+           "--vault-cacert", vault_cacert] + vault_auth
+
+    module.run_command(cmd, check_rc=True,
+                       path_prefix=virtualenv_path_prefix(module))
+
 
 def create_vault_password_file(module):
     """Create a vault password file."""
@@ -128,6 +171,33 @@ def kolla_passwords(module):
             finally:
                 os.unlink(src_path)
 
+        if module.params['vault_addr']:
+            src_path = create_named_tempfile()
+            try:
+                shutil.copyfile(module.params['src'], src_path)
+                kolla_readpwd(module, src_path,
+                              module.params['vault_addr'],
+                              module.params['vault_mount_point'],
+                              module.params['vault_kv_path'],
+                              module.params['vault_namespace'],
+                              module.params['vault_role_id'],
+                              module.params['vault_secret_id'],
+                              module.params['vault_token'],
+                              module.params['vault_cacert'])
+                kolla_mergepwd(module, src_path, temp_file_path, temp_file_path)
+                kolla_genpwd(module, temp_file_path)
+                kolla_writepwd(module, temp_file_path,
+                              module.params['vault_addr'],
+                              module.params['vault_mount_point'],
+                              module.params['vault_kv_path'],
+                              module.params['vault_namespace'],
+                              module.params['vault_role_id'],
+                              module.params['vault_secret_id'],
+                              module.params['vault_token'],
+                              module.params['vault_cacert'])
+            finally:
+                os.unlink(src_path)
+
         # Merge in overrides.
         if module.params['overrides']:
             with tempfile.NamedTemporaryFile(delete=False) as f:
@@ -137,6 +207,16 @@ def kolla_passwords(module):
                 overrides_path = f.name
             try:
                 kolla_mergepwd(module, overrides_path, temp_file_path, temp_file_path)
+                if module.params['vault_addr']:
+                    kolla_writepwd(module, temp_file_path,
+                                   module.params['vault_addr'],
+                                   module.params['vault_mount_point'],
+                                   module.params['vault_kv_path'],
+                                   module.params['vault_namespace'],
+                                   module.params['vault_role_id'],
+                                   module.params['vault_secret_id'],
+                                   module.params['vault_token'],
+                                   module.params['vault_cacert'])
             finally:
                 os.unlink(overrides_path)
 
@@ -189,10 +269,23 @@ def main():
             sample=dict(default='/usr/share/kolla-ansible/etc_examples/kolla/passwords.yml', type='str'),
             src=dict(default='/etc/kolla/passwords.yml', type='str'),
             vault_password=dict(type='str', no_log=True),
+            vault_addr=dict(type='str', no_log=False),
+            vault_mount_point=dict(type='str', no_log=False),
+            vault_kv_path=dict(type='str', no_log=False),
+            vault_namespace=dict(type='str', no_log=False),
+            vault_role_id=dict(type='str', no_log=True),
+            vault_secret_id=dict(type='str', no_log=True),
+            vault_token=dict(type='str', no_log=True),
+            vault_cacert=dict(type='str', no_log=False),
             virtualenv=dict(type='str'),
         ),
         add_file_common_args=True,
         supports_check_mode=True,
+        required_together=[['vault_mount_point', 'vault_addr'],
+                            ['vault_role_id', 'vault_secret_id'],
+                            ['vault_mount_point','vault_kv_path']],
+        mutually_exclusive=[['vault_token', 'vault_role_id'],
+                            ['vault_token', 'vault_secret_id']]
     )
 
     if IMPORT_ERRORS:
diff --git a/ansible/roles/kolla-ansible/tasks/config.yml b/ansible/roles/kolla-ansible/tasks/config.yml
index 8f4ad0a2..917e996a 100644
--- a/ansible/roles/kolla-ansible/tasks/config.yml
+++ b/ansible/roles/kolla-ansible/tasks/config.yml
@@ -98,6 +98,14 @@
     sample: "{{ kolla_ansible_install_dir }}/etc_examples/kolla/passwords.yml"
     overrides: "{{ kolla_ansible_custom_passwords }}"
     vault_password: "{{ kolla_ansible_vault_password }}"
+    vault_addr: "{{ kolla_ansible_vault_addr }}"
+    vault_mount_point: "{{ kolla_ansible_vault_mount_point }}"
+    vault_kv_path: "{{ kolla_ansible_vault_kv_path }}"
+    vault_namespace: "{{ kolla_ansible_vault_namespace }}"
+    vault_role_id: "{{ kolla_ansible_vault_role_id or omit }}"
+    vault_secret_id: "{{ kolla_ansible_vault_secret_id or omit }}"
+    vault_token: "{{ kolla_ansible_vault_token or omit }}"
+    vault_cacert: "{{ kolla_ansible_vault_cacert }}"
     virtualenv: "{{ kolla_ansible_venv or omit }}"
 
 - name: Ensure the Kolla passwords file is copied into place
diff --git a/etc/kayobe/kolla.yml b/etc/kayobe/kolla.yml
index 2d975b26..dfacff22 100644
--- a/etc/kayobe/kolla.yml
+++ b/etc/kayobe/kolla.yml
@@ -235,6 +235,19 @@
 # remotely on the target nodes. If None, no virtualenv will be used.
 #kolla_ansible_target_venv:
 
+# Password to use to encrypt the kolla-ansible passwords.yml file.
+#kolla_ansible_vault_password:
+
+# Hashi Vault
+#kolla_ansible_vault_addr:
+#kolla_ansible_vault_mount_point:
+#kolla_ansible_vault_kv_path:
+#kolla_ansible_vault_namespace:
+#kolla_ansible_vault_role_id:
+#kolla_ansible_vault_secret_id:
+#kolla_ansible_vault_token:
+#kolla_ansible_vault_cacert:
+
 # Whether TLS is enabled for the external API endpoints. Default is 'no'.
 #kolla_enable_tls_external:
 
diff --git a/releasenotes/notes/hashi-vault-kolla-passwords-c4584aeeecd36e80.yaml b/releasenotes/notes/hashi-vault-kolla-passwords-c4584aeeecd36e80.yaml
new file mode 100644
index 00000000..ad7ed7a6
--- /dev/null
+++ b/releasenotes/notes/hashi-vault-kolla-passwords-c4584aeeecd36e80.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Adds functionality into the kolla_passwords module to allow passwords that
+    are generated for Kolla Ansible to be stored in Hashicorp Vault.
diff --git a/requirements.txt b/requirements.txt
index 2f4e5b8f..19decf8a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,3 +9,4 @@ selinux # MIT
 oslo.config>=5.2.0 # Apache-2.0
 paramiko # LGPL
 jsonschema<5 # MIT
+hvac>=0.10.1
-- 
GitLab