From 37ae881500fdeb31e265770f1d9e006d74aa04bc Mon Sep 17 00:00:00 2001 From: Stefan Dinu Date: Tue, 13 Aug 2019 15:44:52 +0300 Subject: [PATCH] Enchance Vault utils --- .../ansible/filter_plugins/commonfilters.py | 18 +++++++- src/ops/cli/terraform.py | 14 ++++++ src/ops/simplevault.py | 44 +++++++++++-------- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/ops/ansible/filter_plugins/commonfilters.py b/src/ops/ansible/filter_plugins/commonfilters.py index 98a38460..8a33ee5a 100644 --- a/src/ops/ansible/filter_plugins/commonfilters.py +++ b/src/ops/ansible/filter_plugins/commonfilters.py @@ -64,6 +64,20 @@ def flatten_tree(d, parent_key='', sep='/'): items.append((new_key, v)) return dict(items) +def check_vault( + secret_path, key='value', vault_user=None, vault_url=None, + token=None, namespace=None, mount_point=None, auto_prompt=True): + + from ops.simplevault import SimpleVault + sv = SimpleVault( + vault_user=vault_user, vault_addr=vault_url, vault_token=token, + namespace=namespace, mount_point=mount_point, auto_prompt=auto_prompt) + check_status = sv.check(secret_path, key) + # we want to return these string values because this is what Jinja2 understands + if check_status: + return "true" + return "false" + def read_vault( secret_path, key='value', fetch_all=False, vault_user=None, vault_url=None, token=None, namespace=None, mount_point=None, auto_prompt=True): @@ -136,9 +150,9 @@ def filters(self): 'read_file': read_file, 'read_vault': read_vault, 'read_yaml': read_yaml, - 'read_vault': read_vault, 'write_vault': write_vault, 'managed_vault_secret': managed_vault_secret, 'read_ssm': read_ssm, - 'escape_json': escape_json + 'escape_json': escape_json, + 'check_vault': check_vault } diff --git a/src/ops/cli/terraform.py b/src/ops/cli/terraform.py index 537c10df..ffd4bfda 100644 --- a/src/ops/cli/terraform.py +++ b/src/ops/cli/terraform.py @@ -14,6 +14,8 @@ from ops.cli.parser import SubParserConfig from ops.terraform.terraform_cmd_generator import TerraformCommandGenerator from ops.hierarchical.composition_config_generator import TerraformConfigGenerator +from distutils.version import StrictVersion +import pkg_resources logger = logging.getLogger(__name__) @@ -136,7 +138,19 @@ def __init__(self, root_dir, cluster_config_path, cluster_config, inventory_gene self.template = template self.execute = execute + def check_ops_version(self): + # Check if the cluster_config has a strict requirement of OPS version + # But only if 'ops_min_version' is specified. Not all clusters configs enforce this + if "terraform" in self.cluster_config.conf: + if "ops_min_version" in self.cluster_config.conf["terraform"]: + ops_min_version = str(self.cluster_config.conf["terraform"]["ops_min_version"]) + current_ops_version = [x.version for x in pkg_resources.working_set if x.project_name == "ops"][0] + if StrictVersion(current_ops_version) < StrictVersion(ops_min_version): + raise Exception("The current ops version {0} is lower than the minimum required version {1} for cluster {2}".format( + current_ops_version, ops_min_version, self.cluster_config_path)) + def run(self, args): + self.check_ops_version() if os.path.isdir(self.cluster_config_path): return self.run_v2_integration(args) else: diff --git a/src/ops/simplevault.py b/src/ops/simplevault.py index 266c7186..48f40c1d 100644 --- a/src/ops/simplevault.py +++ b/src/ops/simplevault.py @@ -79,31 +79,39 @@ def write_token(token=None): raise e else: pass - def get(self, path, key='value', wrap_ttl=None, default=None, fetch_all=False, raise_exceptions=False, raw=False): + def get(self, path, key='value', wrap_ttl=None, default=None, fetch_all=False, raw=False): if raw: fetch_all = True if fetch_all: key=None + raw_data = self.vault_conn.secrets.kv.v2.read_secret_version( + path=path, mount_point=self.mount_point) + # move this check earlier, and, if true, return immediately + if raw: + return raw_data + data = raw_data.get('data') + if isinstance(data, dict): + if not fetch_all: + if key: + # the actual secret k v pairs are nested under another dictionary key "data" + return data.get("data").get(key, default) + else: + raise('VAULT-LIB: either key or fetch_all should be set!') + + def check(self, path, key): + # somewhat boilerplate method that returns a boolean whether the provided secret exists + # and if it has the desired key, with a non-empty value try: raw_data = self.vault_conn.secrets.kv.v2.read_secret_version( path=path, mount_point=self.mount_point) - # move this check earlier, and, if true, return immediately - if raw: - return raw_data - data = raw_data.get('data') - if isinstance(data, dict): - if not fetch_all: - if key: - # the actual secret k v pairs are nested under another dictionary key "data" - return data.get("data").get(key, default) - else: - raise('VAULT-LIB: either key or fetch_all should be set!') + if key not in raw_data["data"]["data"]: + return False + if raw_data["data"]["data"][key] is None: + return False except Exception as e: - if raise_exceptions: - raise e - else: - data = default - return data + # if the provided secret path doesn't exist, return false + return False + return True def put(self, path, value, lease=None, wrap_ttl=None): payload = {} @@ -161,7 +169,7 @@ def __init__( display('MANAGED-SECRET: could not obtain a proper Vault connection.\n{}'.format(e.message)) raise e try: - self.current_data = self.sv.get(path=path, fetch_all=True, raise_exceptions=True) + self.current_data = self.sv.get(path=path, fetch_all=True) except Exception as e: display('MANAGED-SECRET: could not confirm if secret at path {} does or not already exist. ' 'Exception was:\n{}'.format(path,e.message))