diff --git a/salt/modules/vault.py b/salt/modules/vault.py index 18f7d127f577..0835cc82ca7d 100644 --- a/salt/modules/vault.py +++ b/salt/modules/vault.py @@ -156,6 +156,9 @@ def read_secret(path, key=None): first: {{ supersecret.first }} second: {{ supersecret.second }} ''' + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] log.debug('Reading Vault secret for %s at %s', __grains__['id'], path) try: url = 'v1/{0}'.format(path) @@ -184,6 +187,10 @@ def write_secret(path, **kwargs): ''' log.debug('Writing vault secrets for %s at %s', __grains__['id'], path) data = dict([(x, y) for x, y in kwargs.items() if not x.startswith('__')]) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] + data = {'data': data} try: url = 'v1/{0}'.format(path) response = __utils__['vault.make_request']('POST', url, json=data) @@ -208,6 +215,10 @@ def write_raw(path, raw): salt '*' vault.write_raw "secret/my/secret" '{"user":"foo","password": "bar"}' ''' log.debug('Writing vault secrets for %s at %s', __grains__['id'], path) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] + raw = {'data': raw} try: url = 'v1/{0}'.format(path) response = __utils__['vault.make_request']('POST', url, json=raw) @@ -232,6 +243,9 @@ def delete_secret(path): salt '*' vault.delete_secret "secret/my/secret" ''' log.debug('Deleting vault secrets for %s in %s', __grains__['id'], path) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] try: url = 'v1/{0}'.format(path) response = __utils__['vault.make_request']('DELETE', url) @@ -255,6 +269,9 @@ def list_secrets(path): salt '*' vault.list_secrets "secret/my/" ''' log.debug('Listing vault secret keys for %s in %s', __grains__['id'], path) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['metadata'] try: url = 'v1/{0}'.format(path) response = __utils__['vault.make_request']('LIST', url) diff --git a/salt/pillar/vault.py b/salt/pillar/vault.py index 3bce53bb0545..8dbe4761f074 100644 --- a/salt/pillar/vault.py +++ b/salt/pillar/vault.py @@ -158,6 +158,10 @@ def ext_pillar(minion_id, # pylint: disable=W0613 try: path = paths[0].replace('path=', '') path = path.format(**{'minion': minion_id}) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] + url = 'v1/{0}'.format(path) response = __utils__['vault.make_request']('GET', url) if response.status_code == 200: diff --git a/salt/sdb/vault.py b/salt/sdb/vault.py index 598054333bdc..3ecf806271ea 100644 --- a/salt/sdb/vault.py +++ b/salt/sdb/vault.py @@ -68,6 +68,10 @@ def set_(key, value, profile=None): else: path, key = key.rsplit('/', 1) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] + try: url = 'v1/{0}'.format(path) data = {key: value} @@ -101,6 +105,10 @@ def get(key, profile=None): else: path, key = key.rsplit('/', 1) + version2 = __utils__['vault.is_v2'](path) + if version2['v2']: + path = version2['data'] + try: url = 'v1/{0}'.format(path) response = __utils__['vault.make_request']('GET', url, profile) diff --git a/salt/utils/vault.py b/salt/utils/vault.py index 4f9085e755da..a9372f252242 100644 --- a/salt/utils/vault.py +++ b/salt/utils/vault.py @@ -204,3 +204,91 @@ def _wrapped_token_valid(): raise salt.exceptions.CommandExecutionError( 'Error while looking up wrapped token : {0}'.format(e) ) + + +def is_v2(path): + ''' + Determines if a given secret path is kv version 1 or 2 + + CLI Example: + + .. code-block:: bash + + salt '*' vault.is_v2 "secret/my/secret" + ''' + ret = {'v2': False, 'data': path, 'metadata': path, 'type': None} + path_metadata = _get_secret_path_metadata(path) + ret['type'] = path_metadata.get('type', 'kv') + if ret['type'] == 'kv' and path_metadata.get('options', {}).get('version', '1') in ['2']: + ret['v2'] = True + ret['data'] = _v2_the_path(path, path_metadata.get('path', path)) + ret['metadata'] = _v2_the_path(path, path_metadata.get('path', path), 'metadata') + return ret + + +def _v2_the_path(path, pfilter, ptype='data'): + ''' + Given a path, a filter, and a path type, properly inject 'data' or 'metadata' into the path + + CLI Example: + + .. code-block:: python + + _v2_the_path('dev/secrets/fu/bar', 'dev/secrets', 'data') => 'dev/secrets/data/fu/bar' + ''' + possible_types = ['data', 'metadata'] + assert ptype in possible_types + msg = "Path {} already contains {} in the right place - saltstack duct tape?".format(path, ptype) + + path = path.rstrip('/').lstrip('/') + pfilter = pfilter.rstrip('/').lstrip('/') + + together = pfilter + '/' + ptype + + otype = possible_types[0] if possible_types[0] != ptype else possible_types[1] + other = pfilter + '/' + otype + if path.startswith(other): + path = path.replace(other, together, 1) + msg = 'Path is a "{}" type but "{}" type requested - Flipping: {}'.format(otype, ptype, path) + elif not path.startswith(together): + msg = "Converting path to v2 {} => {}".format(path, path.replace(pfilter, together, 1)) + path = path.replace(pfilter, together, 1) + + log.debug(msg) + return path + + +def _get_secret_path_metadata(path): + ''' + Given a path query vault to determine where the mount point is, it's type and version + + CLI Example: + + .. code-block:: python + + _get_secret_path_metadata('dev/secrets/fu/bar') + ''' + ckey = 'vault_secret_path_metadata' + if ckey not in __context__: + __context__[ckey] = {} + + ret = None + if path.startswith(tuple(__context__[ckey].keys())): + log.debug('Found cached metadata for %s in %s', __grains__['id'], path) + ret = next(v for k, v in __context__[ckey].items() if path.startswith(k)) + else: + log.debug('Fetching metadata for %s in %s', __grains__['id'], path) + try: + url = 'v1/sys/internal/ui/mounts/{0}'.format(path) + response = make_request('GET', url) + if response.ok: + response.raise_for_status() + if response.json().get('data', False): + log.debug('Got metadata for %s in %s', __grains__['id'], path) + ret = response.json()['data'] + __context__[ckey][path] = ret + else: + raise response.json() + except Exception as err: + log.error('Failed to list secrets! %s: %s', type(err).__name__, err) + return ret