aboutsummaryrefslogtreecommitdiff
path: root/kolla_ansible
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2021-07-02 20:28:44 +0000
committerGerrit Code Review <review@openstack.org>2021-07-02 20:28:44 +0000
commit2ecf0a8783a71777ab1082329bb3ae778f41e949 (patch)
tree40de3196e1988f2903f81efd3bb83a8edf63e936 /kolla_ansible
parente63d985ccb111eb8aeb3b02a41d8ce04acdb7016 (diff)
parent6bf74aa20d268f11f676a0e9affa92e3022b595d (diff)
downloadkolla-ansible-2ecf0a8783a71777ab1082329bb3ae778f41e949.tar.gz
kolla-ansible-2ecf0a8783a71777ab1082329bb3ae778f41e949.zip
Merge "Support storing passwords in Hashicorp Vault"
Diffstat (limited to 'kolla_ansible')
-rwxr-xr-xkolla_ansible/cmd/readpwd.py118
-rwxr-xr-xkolla_ansible/cmd/writepwd.py120
-rw-r--r--kolla_ansible/hashi_vault.py64
3 files changed, 302 insertions, 0 deletions
diff --git a/kolla_ansible/cmd/readpwd.py b/kolla_ansible/cmd/readpwd.py
new file mode 100755
index 000000000..87bdf6ff5
--- /dev/null
+++ b/kolla_ansible/cmd/readpwd.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import os
+import sys
+
+import hvac
+import yaml
+
+from kolla_ansible.hashi_vault import hashicorp_vault_client
+
+
+def readpwd(passwords_file, vault_kv_path, vault_mount_point, vault_namespace,
+ vault_addr, vault_role_id, vault_secret_id, vault_token,
+ vault_cacert):
+
+ with open(passwords_file, 'r') as f:
+ passwords = yaml.safe_load(f.read())
+
+ if not isinstance(passwords, dict):
+ print("ERROR: Passwords file not in expected key/value format")
+ sys.exit(1)
+
+ client = hashicorp_vault_client(vault_namespace, vault_addr, vault_role_id,
+ vault_secret_id, vault_token, vault_cacert)
+
+ vault_kv_passwords = dict()
+ for password_key in passwords:
+ try:
+ password_data = client.secrets.kv.v2.read_secret_version(
+ mount_point=vault_mount_point,
+ path="{}/{}".format(vault_kv_path, password_key))
+ except hvac.exceptions.InvalidPath:
+ # Ignore passwords that are not found in Vault
+ print("WARNING: '%s' not found in Vault" % password_key)
+ vault_kv_passwords[password_key] = None
+ continue
+ try:
+ vault_kv_passwords[password_key] =\
+ password_data['data']['data']['password']
+ except KeyError:
+ vault_kv_passwords[password_key] = password_data['data']['data']
+
+ with open(passwords_file, 'w') as f:
+ yaml.safe_dump(vault_kv_passwords, f)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '-p', '--passwords', type=str,
+ default=os.path.abspath('/etc/kolla/passwords.yml'),
+ help='Path to the passwords.yml file')
+ parser.add_argument(
+ '-kv', '--vault-mount-point', type=str,
+ default='kv',
+ help='Path to the KV mount point')
+ parser.add_argument(
+ '-kvp', '--vault-kv-path', type=str,
+ default='kolla_passwords',
+ help='Path to store passwords within your configured KV mount point')
+ parser.add_argument(
+ '-n', '--vault-namespace', type=str,
+ default='',
+ help='Vault namespace (enterprise only)')
+ parser.add_argument(
+ '-v', '--vault-addr', type=str,
+ required=True,
+ help='Address to connect to an existing Hashicorp Vault')
+ parser.add_argument(
+ '-r', '--vault-role-id', type=str,
+ default='',
+ help='Role-ID to authenticate to Vault. This must be used in '
+ 'conjunction with --secret-id')
+ parser.add_argument(
+ '-s', '--vault-secret-id', type=str,
+ default='',
+ help='Secret-ID to authenticate to Vault. This must be used in '
+ 'conjunction with --role-id')
+ parser.add_argument(
+ '-t', '--vault-token', type=str,
+ default='',
+ help='Vault token to authenticate to Vault')
+ parser.add_argument(
+ '-c', '--vault-cacert', type=str,
+ default='',
+ help='Path to CA certificate file')
+
+ args = parser.parse_args()
+ passwords_file = os.path.expanduser(args.passwords)
+ vault_kv_path = args.vault_kv_path
+ vault_mount_point = args.vault_mount_point
+ vault_namespace = args.vault_namespace
+ vault_addr = args.vault_addr
+ vault_role_id = args.vault_role_id
+ vault_secret_id = args.vault_secret_id
+ vault_token = args.vault_token
+ vault_cacert = os.path.expanduser(args.vault_cacert)
+
+ readpwd(passwords_file, vault_kv_path, vault_mount_point, vault_namespace,
+ vault_addr, vault_role_id, vault_secret_id, vault_token,
+ vault_cacert)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/kolla_ansible/cmd/writepwd.py b/kolla_ansible/cmd/writepwd.py
new file mode 100755
index 000000000..9a3eb0d81
--- /dev/null
+++ b/kolla_ansible/cmd/writepwd.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import os
+import sys
+
+import hvac
+import yaml
+
+from kolla_ansible.hashi_vault import hashicorp_vault_client
+
+
+def writepwd(passwords_file, vault_kv_path, vault_mount_point, vault_namespace,
+ vault_addr, vault_role_id, vault_secret_id, vault_token,
+ vault_cacert):
+ with open(passwords_file, 'r') as f:
+ passwords = yaml.safe_load(f.read())
+
+ if not isinstance(passwords, dict):
+ print("ERROR: Passwords file not in expected key/value format")
+ sys.exit(1)
+
+ client = hashicorp_vault_client(vault_namespace, vault_addr, vault_role_id,
+ vault_secret_id, vault_token, vault_cacert)
+
+ for key, value in passwords.items():
+ # Ignore empty values
+ if not value:
+ continue
+
+ if isinstance(value, str):
+ value = dict(password=value)
+
+ try:
+ remote_value = client.secrets.kv.v2.read_secret_version(
+ mount_point=vault_mount_point,
+ path="{}/{}".format(vault_kv_path, key))
+ except hvac.exceptions.InvalidPath:
+ # Add to KV if value does not exists
+ remote_value = None
+
+ # Update KV is value has changed or it does not exist
+ if not remote_value or remote_value['data']['data'] != value:
+ client.secrets.kv.v2.create_or_update_secret(
+ mount_point=vault_mount_point,
+ path="{}/{}".format(vault_kv_path, key),
+ secret=value)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '-p', '--passwords', type=str,
+ default=os.path.abspath('/etc/kolla/passwords.yml'),
+ help='Path to the passwords.yml file')
+ parser.add_argument(
+ '-kv', '--vault-mount-point', type=str,
+ default='kv',
+ help='Path to the KV mount point')
+ parser.add_argument(
+ '-kvp', '--vault-kv-path', type=str,
+ default='kolla_passwords',
+ help='Path to store passwords within your configured KV mount point')
+ parser.add_argument(
+ '-n', '--vault-namespace', type=str,
+ default='',
+ help='Vault namespace (enterprise only)')
+ parser.add_argument(
+ '-v', '--vault-addr', type=str,
+ required=True,
+ help='Address to connect to an existing Hashicorp Vault')
+ parser.add_argument(
+ '-r', '--vault-role-id', type=str,
+ default='',
+ help='Role-ID to authenticate to Vault. This must be used in '
+ 'conjunction with --secret-id')
+ parser.add_argument(
+ '-s', '--vault-secret-id', type=str,
+ default='',
+ help='Secret-ID to authenticate to Vault. This must be used in '
+ 'conjunction with --role-id')
+ parser.add_argument(
+ '-t', '--vault-token', type=str,
+ default='',
+ help='Vault token to authenticate to Vault')
+ parser.add_argument(
+ '-c', '--vault-cacert', type=str,
+ default='',
+ help='Path to CA certificate file')
+
+ args = parser.parse_args()
+ passwords_file = os.path.expanduser(args.passwords)
+ vault_kv_path = args.vault_kv_path
+ vault_mount_point = args.vault_mount_point
+ vault_namespace = args.vault_namespace
+ vault_addr = args.vault_addr
+ vault_role_id = args.vault_role_id
+ vault_secret_id = args.vault_secret_id
+ vault_token = args.vault_token
+ vault_cacert = os.path.expanduser(args.vault_cacert)
+
+ writepwd(passwords_file, vault_kv_path, vault_mount_point, vault_namespace,
+ vault_addr, vault_role_id, vault_secret_id, vault_token,
+ vault_cacert)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/kolla_ansible/hashi_vault.py b/kolla_ansible/hashi_vault.py
new file mode 100644
index 000000000..de0423550
--- /dev/null
+++ b/kolla_ansible/hashi_vault.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+import hvac
+
+
+def hashicorp_vault_client(vault_namespace, vault_addr, vault_role_id,
+ vault_secret_id, vault_token, vault_cacert):
+ """Connect to a Vault sever and create a client.
+
+ :param vault_namespace: Vault namespace (enterprise only).
+ :param vault_addr: Address to connect to an existing Hashicorp Vault.
+ :param vault_role_id: Role-ID to authenticate to Vault. This must be used
+ in conjunction with --secret-id.
+ :param vault_secret_id: Secret-ID to authenticate to Vault. This must be
+ used in conjunction with --role-id.
+ :param vault_token: Vault token to authenticate to Vault.
+ :param vault_cacert: Path to CA certificate file.
+ :returns: Hashicorp Vault Client (hvac.Client).
+ """
+
+ if any([vault_role_id, vault_secret_id]):
+ if vault_token:
+ print("ERROR: Vault token cannot be used at the same time as "
+ "role-id and secret-id")
+ sys.exit(1)
+ if not all([vault_role_id, vault_secret_id]):
+ print("ERROR: role-id and secret-id must be provided together")
+ sys.exit(1)
+ elif not vault_token:
+ print("ERROR: You must provide either a Vault token or role-id and "
+ "secret-id")
+ sys.exit(1)
+
+ # Authenticate to Hashicorp Vault
+ if vault_cacert != "":
+ os.environ['REQUESTS_CA_BUNDLE'] = vault_cacert
+
+ if vault_token != "": # nosec
+ client = hvac.Client(url=vault_addr, token=vault_token,
+ namespace=vault_namespace)
+ else:
+ client = hvac.Client(url=vault_addr, namespace=vault_namespace)
+ client.auth_approle(vault_role_id, vault_secret_id)
+
+ if not client.is_authenticated():
+ print('Failed to authenticate to vault')
+ sys.exit(1)
+
+ return client