getting there

This commit is contained in:
brent s 2020-04-02 14:55:26 -04:00
parent 93e45e8db9
commit e178261c31
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
6 changed files with 124 additions and 48 deletions

1
.gitignore vendored
View File

@ -24,6 +24,7 @@ testing/data
testing/test.pid testing/test.pid
testing/testserver.json testing/testserver.json
testing/vault.log testing/vault.log
testing/test.config.xml
__pycache__/ __pycache__/
logs/ logs/
docs/README.html docs/README.html

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3


import copy
import json import json
import os import os


@ -25,7 +26,7 @@ log_file = os.path.abspath(os.path.expanduser(log_file))


def genConf(confdict = None): def genConf(confdict = None):
if not confdict: if not confdict:
confdict = default_conf.copy() confdict = copy.deepcopy(default_conf)
storage = confdict.get('storage') storage = confdict.get('storage')
if storage: if storage:
if 'file' in storage.keys(): if 'file' in storage.keys():

View File

@ -10,11 +10,12 @@ import subprocess
import time import time
## ##
import hvac import hvac
import hvac.exceptions
import psutil import psutil
from lxml import etree from lxml import etree
## ##
from . import serverconf import serverconf
from . import vaultpassconf from vaultpass import config as vaultpassconf




_url_re = re.compile(r'^(?P<proto>https?)://(?P<addr>[^:/]+)(:(?P<port>[0-9]+)?)?(?P<path>/.*)?$') _url_re = re.compile(r'^(?P<proto>https?)://(?P<addr>[^:/]+)(:(?P<port>[0-9]+)?)?(?P<path>/.*)?$')
@ -30,6 +31,7 @@ class VaultSpawner(object):
is_running = None is_running = None
local = True local = True
pid = None pid = None
is_new = False


def __init__(self, conf, genconf = True, clientconf_file = './test.config.xml', test_data = True, *args, **kwargs): def __init__(self, conf, genconf = True, clientconf_file = './test.config.xml', test_data = True, *args, **kwargs):
self.conf = conf self.conf = conf
@ -54,7 +56,10 @@ class VaultSpawner(object):
try: try:
self.port = int(r.groupdict().get('port', 80)) self.port = int(r.groupdict().get('port', 80))
except ValueError: except ValueError:
if is_not_tls:
self.port = 80 self.port = 80
else:
self.port = 443
self.ip = socket.gethostbyname(r.groupdict()['addr']) self.ip = socket.gethostbyname(r.groupdict()['addr'])
sock = socket.socket() sock = socket.socket()
try: try:
@ -65,14 +70,19 @@ class VaultSpawner(object):
self.is_running = False self.is_running = False
except OSError as e: except OSError as e:
if e.errno == 98: if e.errno == 98:
# Already in use
self.is_running = True self.is_running = True
elif e.errno == 99: elif e.errno == 99:
# The address isn't on this box. # The address isn't on this box.
self.local = False self.local = False
self.pid = None self.pid = None
finally:
sock.close()
sock = socket.socket()
sock.connect((self.ip, self.port)) sock.connect((self.ip, self.port))
sock.close() sock.close()
except (ConnectionRefusedError, ConnectionAbortedError, ConnectionResetError): self.is_running = True
except (ConnectionRefusedError, ConnectionAbortedError, ConnectionResetError) as e:
self.is_running = False self.is_running = False
finally: finally:
try: try:
@ -89,31 +99,36 @@ class VaultSpawner(object):
self.client_conf = vaultpassconf.getConfig(clientconf) self.client_conf = vaultpassconf.getConfig(clientconf)
return(None) return(None)


def _getCreds(self, new_unseal = None, new_auth = None, write_conf = None): def _getCreds(self, new_unseal = False, new_token = False, write_conf = None):
if not write_conf: if not write_conf:
write_conf = self.clientconf_file write_conf = self.clientconf_file
self._getClientConf() self._getClientConf()
rewrite_xml = False rewrite_xml = False
# TODO: finish regen of client conf and re-parse so new elements get added to both, xml = self.client_conf.xml
# and write out namespaced xml ns_xml = self.client_conf.namespaced_xml
unseal_xml = self.client_conf.namespaced_xml.find('.//{0}unseal'.format(self.client_conf.xml.nsmap[None])) nsmap = ns_xml.nsmap
self.unseal = unseal_xml.text unseal_ns_xml = ns_xml.find('.//{{{0}}}unseal'.format(nsmap[None]))
auth_xml = self.client_conf.xml.find('.//{0}auth'.format(self.client_conf.xml.nsmap[None])) unseal_xml = xml.find('.//unseal')
auth_ns_xml = ns_xml.find('.//{{{0}}}auth'.format(nsmap[None]))
auth_xml = xml.find('.//auth')
token_ns_xml = auth_ns_xml.find('.//{{{0}}}token'.format(nsmap[None]))
token_xml = auth_xml.find('.//token') token_xml = auth_xml.find('.//token')
if unseal_xml is not None and not new_unseal: if not new_unseal:
self.unseal = unseal_xml.text self.unseal = unseal_xml.text
if token_xml is not None and not new_auth: else:
self.client.token = token_xml.text unseal_xml.text = self.unseal
if new_unseal: unseal_ns_xml.text = self.unseal
unseal_xml.getparent().replace(unseal_xml, new_unseal)
rewrite_xml = True rewrite_xml = True
if new_auth: if not new_token:
auth_xml.getparent().replace(auth_xml, new_auth) self.client.token = token_xml.text
else:
token_xml.text = self.client.token
token_ns_xml.text = self.client.token
rewrite_xml = True rewrite_xml = True
if rewrite_xml: if rewrite_xml:
write_conf = os.path.abspath(os.path.expanduser(write_conf)) write_conf = os.path.abspath(os.path.expanduser(write_conf))
with open(write_conf, 'w') as fh: with open(write_conf, 'wb') as fh:
fh.write() # TODO: which object? fh.write(self.client_conf.toString())
return(None) return(None)


def _getProcess(self): def _getProcess(self):
@ -135,7 +150,7 @@ class VaultSpawner(object):
# so we have *no* way to get the PID as a regular user. # so we have *no* way to get the PID as a regular user.
raise RuntimeError('Cannot determine Vault instance to manage') raise RuntimeError('Cannot determine Vault instance to manage')
elif len(processes) == 1: elif len(processes) == 1:
self.pid = self.processes[0].pid self.pid = processes[0].pid
else: else:
# We're running as root. # We're running as root.
conns = [c for c in psutil.net_connections() if c.laddr.ip == ip and c.laddr.port == port] conns = [c for c in psutil.net_connections() if c.laddr.ip == ip and c.laddr.port == port]
@ -156,13 +171,8 @@ class VaultSpawner(object):
init_rslt = self.client.sys.initialize(secret_shares = 1, secret_threshold = 1) init_rslt = self.client.sys.initialize(secret_shares = 1, secret_threshold = 1)
self.unseal = init_rslt['keys_base64'][0] self.unseal = init_rslt['keys_base64'][0]
self.client.token = init_rslt['root_token'] self.client.token = init_rslt['root_token']
newauth = etree.Element('auth') self._getCreds(new_token = True, new_unseal = True)
newtoken = etree.Element('token') self.is_new = True
newtoken.text = self.client.token
newauth.append(newtoken)
newunseal = etree.Element('unseal')
newunseal.text = self.unseal
self._getCreds(new_auth = newauth, new_unseal = newunseal)
if self.client.sys.is_sealed(): if self.client.sys.is_sealed():
self.client.sys.submit_unseal_key(self.unseal) self.client.sys.submit_unseal_key(self.unseal)
return(True) return(True)
@ -172,7 +182,8 @@ class VaultSpawner(object):
rawconf = None rawconf = None
if not self.conf: if not self.conf:
if os.path.isfile(serverconf.conf_file): if os.path.isfile(serverconf.conf_file):
self.conf = serverconf.conf_file with open(serverconf.conf_file, 'r') as fh:
self.conf = json.loads(fh.read())
else: else:
# Use the default. # Use the default.
self.genconf = True self.genconf = True
@ -201,31 +212,95 @@ class VaultSpawner(object):
shutil.rmtree(storage) shutil.rmtree(storage)
return(None) return(None)


def populate(self, strict = False):
mounts = {}
if not self.is_running:
self.start()
if not self.is_new and strict:
return(None)
if self.is_new:
opts = {'file_path': serverconf.log_file,
'log_raw': True,
'hmac_accessor': False}
self.client.sys.enable_audit_device(device_type = 'file',
description = 'Testing log',
options = opts)
mount_xml = self.client_conf.xml.find('.//mount')
if mount_xml is not None:
for mount in mount_xml:
mtype = mount.attrib.get('type', 'kv2')
mounts[mount.text] = mtype
else:
# Use a default set.
mounts['secret'] = 'kv2'
mounts['secret_legacy'] = 'kv1'
for idx, (mname, mtype) in enumerate(mounts.items()):
opts = None
orig_mtype = mtype
if mtype.startswith('kv'):
opts = {'version': re.sub(r'^kv([0-9]+)$', r'\g<1>', mtype)}
mtype = 'kv'
try:
self.client.sys.enable_secrets_engine(mtype,
path = mname,
description = 'Testing mount ({0})'.format(mtype),
options = opts)
except hvac.exceptions.InvalidRequest:
# It probably already exists.
pass
if orig_mtype not in ('kv', 'kv2', 'cubbyhole'):
continue
args = {'path': 'test_secret{0}/foo{1}'.format(idx, mname),
'mount_point': mname,
'secret': 'bar{0}'.format(idx)}
handler = None
if orig_mtype == 'cubbyhole':
handler = self.client.write
args['path'] = '{0}/test_secret{1}'.format(mname, idx)
args['foo_{0}'.format(mname)] = 'bar{0}'.format(idx)
del(args['mount_point'])
elif orig_mtype == 'kv1':
handler = self.client.secrets.kv.v1.create_or_update_secret
elif orig_mtype == 'kv2':
handler = self.client.secrets.kv.v2.create_or_update_secret
try:
handler(**args)
except hvac.exceptions.InvalidPath:
print('{0} path invalid'.format(args['path']))
except Exception as e:
print('Exception: {0} ({1})'.format(e, e.__class__))
return(None)

def start(self): def start(self):
self._getProcess() self._getProcess()
if self.is_running: if self.is_running or not self.local:
# Already started. # Already started.
self._initChk() self._initChk()
return(None) return(None)
if not self.local: cmd_str = [self.binary_path, 'server',
# It's a remote address '-config', serverconf.conf_file]
self._initChk() # We have to use .Popen() instead of .run() because even "vault server" doesn't daemonize.
return(None)
cmd_str = [self.binary_path, 'server']
cmd_str.extend(['-config', serverconf.conf_file])
# We have to use Popen because even vault server doesn't daemonize.
# Gorram it, HashiCorp. # Gorram it, HashiCorp.
self.cmd = subprocess.Popen(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE) self.cmd = subprocess.Popen(cmd_str, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
if self.cmd.returncode != 0:
print('STDERR:\n{0}'.format(self.cmd.stderr.decode('utf-8')))
raise RuntimeError('Vault did not start correctly')
attempts = 5 attempts = 5
seconds = 3 seconds = 5
while not self.is_running and attempts != 0: while attempts > 0:
self._connCheck() self._connCheck(bind = False)
if self.is_running:
break
time.sleep(seconds) time.sleep(seconds)
attempts -= 1
if not self.is_running: if not self.is_running:
stdout = self.cmd.stdout.read().decode('utf-8').strip()
stderr = self.cmd.stdout.read().decode('utf-8').strip()
for x in ('stdout', 'stderr'):
if locals()[x] != '':
print('{0}:\n{1}'.format(x.upper(), locals()[x]))
self.cmd.kill()
del(self.cmd)
self.cmd = None
raise TimeoutError('Could not start Vault') raise TimeoutError('Could not start Vault')
time.sleep(2) # We need to make sure we give enough time for it to start up
self._initChk() self._initChk()
return(None) return(None)


@ -234,7 +309,8 @@ class VaultSpawner(object):
if self.cmd: if self.cmd:
self.cmd.kill() self.cmd.kill()
else: else:
os.kill(self.pid) import signal
os.kill(self.pid, signal.SIGKILL)
return(None) return(None)





1
testing/vaultpass Symbolic link
View File

@ -0,0 +1 @@
../vaultpass

View File

@ -1 +0,0 @@
../vaultpass/config.py

View File

@ -209,9 +209,7 @@ class MountHandler(object):
indent = 1 indent = 1
return(pprint.pformat(self.paths, indent = indent)) return(pprint.pformat(self.paths, indent = indent))
# elif output == 'tree': # elif output == 'tree':
# # UNIX tree command output. # import tree # TODO? Wayyy later.
# # has prefixes like ├──, │   ├──, └──, etc.
# import tree
elif not output: elif not output:
return(str(self.paths)) return(str(self.paths))
return(None) return(None)