Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions salt/modules/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ def read_secret(path, key=None):
first: {{ supersecret.first }}
second: {{ supersecret.second }}
'''
version2 = __utils__['vault.is_v2'](path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could have a get_info() that's doing the if, to avoid these 3 lines everywhere. That can return path & data (hence calling it get_info as opposed to get_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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions salt/pillar/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions salt/sdb/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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)
Expand Down
88 changes: 88 additions & 0 deletions salt/utils/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -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