From dc187c92c760f9312ce9dff063a4d4e902b6f51d Mon Sep 17 00:00:00 2001 From: brent s Date: Tue, 31 Mar 2020 06:47:44 -0400 Subject: [PATCH] adding some mount handling. primarily focused on listing for now. --- vaultpass/__init__.py | 5 ++- vaultpass/mounts.py | 102 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/vaultpass/__init__.py b/vaultpass/__init__.py index 7625c39..9ffb835 100644 --- a/vaultpass/__init__.py +++ b/vaultpass/__init__.py @@ -41,8 +41,9 @@ class PassMan(object): return(None) def _getMount(self): - # TODO: mounts xml? - self.mount = mounts.MountHandler(self.client) + mounts_xml = self.xml.find('.//mounts') + self.mount = mounts.MountHandler(self.client, mounts_xml = mounts_xml) + return(None) def _getURI(self): uri = self.cfg.xml.find('.//uri') diff --git a/vaultpass/mounts.py b/vaultpass/mounts.py index d7ba8e0..9b5c900 100644 --- a/vaultpass/mounts.py +++ b/vaultpass/mounts.py @@ -2,11 +2,31 @@ import logging import re import warnings ## +import dpath # https://pypi.org/project/dpath/ import hvac.exceptions _logger = logging.getLogger() -_mount_re = re.compile(r'^(?P.*)/') +_mount_re = re.compile(r'^(?P.*)/$') +_subpath_re = re.compile(r'^/?(?P.*)/$') + + +class CubbyHandler(object): + # There is no upstream support for directly reading cubby. So we do it ourselves. + # TODO: custom class/handler? https://hvac.readthedocs.io/en/stable/advanced_usage.html#custom-requests-http-adapter + def __init__(self, client): + self.client = client + + def list_secrets(self, path, mount_point = 'cubbyhole'): + path = path.lstrip('/') + uri = '/v1/{0}/{1}'.format(mount_point, path) + resp = self.client._adapter.list(url = uri) + return(resp.json()) + + def read_secret(self, *args, **kwargs): + # https://github.com/hashicorp/vault/issues/8644 + _logger.warning('Cannot get path info from a cubbyhole') + return({'data': {}}) class MountHandler(object): @@ -14,8 +34,11 @@ class MountHandler(object): def __init__(self, client, mounts_xml = None): self.client = client + self.cubbyhandler = CubbyHandler(self.client) self.xml = mounts_xml - self.mounts = [] + self.mounts = {} + self.paths = {} + self.getSysMounts() def getSysMounts(self): try: @@ -25,16 +48,81 @@ class MountHandler(object): mount = r.group('mount') if mount in self.internal_mounts: continue - self.mounts.append(mount) - _logger.debug('Added mountpoint to mounts list: {0}'.format(mount)) + # Get the mount type. + mtype = mount_info['type'] + if mtype == 'kv': + mntopts = mount_info['options'] + if mntopts and isinstance(mntopts, dict): + mver = mntopts.get('version') + if mver == '2': + mtype = 'kv2' + elif mver == '1': + mtype = 'kv1' + self.mounts[mount] = mtype + _logger.debug('Added mountpoint {0} to mounts list with type {1}'.format(mount, mtype)) except hvac.exceptions.Forbidden: _logger.warning('Client does not have permission to read /sys/mounts.') - # TODO: xml parsing - + # TODO: should I blindly merge in instead or no? + if self.xml: + for mount in self.xml.findall('.//mount'): + mname = mount.text + mtype = mount.attrib.get('type', 'kv2') + if mname not in self.mounts.keys(): + self.mounts[mname] = mtype + _logger.debug('Added mountpoint {0} to mounts list with type {1}'.format(mount, mtype)) return(None) + def getSecrets(self, path = '/', mounts = None): + if not mounts: + mounts = self.mounts + if isinstance(mounts, dict): + mounts = list(mounts.keys()) + if not isinstance(mounts, list): + mounts = [mounts] + for mount in mounts: + mtype = self.mounts.get(mount) + if not mtype: + _logger.error('Mount not found in defined mounts') + _logger.debug('The mount {0} was not found in the defined mounts.'.format(mount)) + raise ValueError('Mount not found in defined mounts') + handler = None + if mtype == 'cubbyhole': + handler = self.cubbyhandler + elif mtype == 'kv': + handler = self.client.secrets.kv.v1 + elif mtype == 'kv2': + handler = self.client.secrets.kv.v2 + if mount not in self.paths.keys(): + self.paths[mount] = {} + try: + paths = handler.list_secrets(path = path, mount_point = mount) + except hvac.exceptions.InvalidPath: + _logger.error('Path does not exist') + _logger.debug('Path {0} on mount {1} does not exist.'.format(path, mount)) + continue + if 'data' not in paths.keys() or 'keys' not in paths['data'].keys(): + _logger.warning('Mount has no secrets/subdirs') + _logger.debug('The mount {0} has no secrets or subdirectories'.format(mount)) + warnings.warn('Mount has no secrets/subdirs') + for p2 in paths['data']['keys']: + is_dir = False + fullpath = '/'.join((path, p2)).replace('//', '/') + if p2.endswith('/'): + r = _mount_re.search(fullpath) + fullpath = r.group('mount') + is_dir = True + self.paths[mount][fullpath] = None + self.getSecrets(path = p2, mounts = mount) + sep_p2 = [i for i in fullpath.split('/') if i.strip() != ''] + if is_dir: + pass + # print(mount, sep_p2) + + def print(self): - pass + import pprint + pprint.pprint(self.paths) + return(None) def search(self): pass