diff options
| author | Zuul <zuul@review.opendev.org> | 2021-07-02 20:28:44 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2021-07-02 20:28:44 +0000 |
| commit | 2ecf0a8783a71777ab1082329bb3ae778f41e949 (patch) | |
| tree | 40de3196e1988f2903f81efd3bb83a8edf63e936 /kolla_ansible | |
| parent | e63d985ccb111eb8aeb3b02a41d8ce04acdb7016 (diff) | |
| parent | 6bf74aa20d268f11f676a0e9affa92e3022b595d (diff) | |
| download | kolla-ansible-2ecf0a8783a71777ab1082329bb3ae778f41e949.tar.gz kolla-ansible-2ecf0a8783a71777ab1082329bb3ae778f41e949.zip | |
Merge "Support storing passwords in Hashicorp Vault"
Diffstat (limited to 'kolla_ansible')
| -rwxr-xr-x | kolla_ansible/cmd/readpwd.py | 118 | ||||
| -rwxr-xr-x | kolla_ansible/cmd/writepwd.py | 120 | ||||
| -rw-r--r-- | kolla_ansible/hashi_vault.py | 64 |
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 |
