141 lines
3.9 KiB
Python
141 lines
3.9 KiB
Python
import os
|
|
import sys
|
|
import time
|
|
import yaml
|
|
import subprocess
|
|
|
|
|
|
def run(cmd, input=None):
|
|
"""
|
|
Run script, storing various aspects of the results.
|
|
"""
|
|
data = {}
|
|
data['start_time'] = time.time()
|
|
proc = subprocess.Popen(cmd,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
close_fds=True,
|
|
)
|
|
#print("Run command {!r} with input {}".format(cmd, input))
|
|
(stdout, stderr) = proc.communicate(input=input)
|
|
data['end_time'] = time.time()
|
|
data['exit_status'] = proc.returncode
|
|
data['pid'] = proc.pid
|
|
data['output'] = stdout
|
|
data['stderr'] = stderr
|
|
data['success'] = True
|
|
if stderr is not None or proc.returncode:
|
|
sys.stderr.write('Command {!r} FAILED (exit {}), stdout {} stderr: {}\n'.format(
|
|
' '.join(cmd), proc.returncode, stdout, stderr))
|
|
data['success'] = False
|
|
return data
|
|
|
|
def extract_comments(data):
|
|
comments = {}
|
|
curr = []
|
|
for this in data.split('\n'):
|
|
if this.startswith('#'):
|
|
#print("COMMENT: {}".format(this))
|
|
curr += [this]
|
|
elif ':' in this and len(curr):
|
|
key = this.split(':')[0]
|
|
comments[key] = curr
|
|
#print("KEY {} FROM {}: {}".format(key, this, curr))
|
|
curr = []
|
|
else:
|
|
#print("RESET AT {}".format(this))
|
|
curr = []
|
|
return comments
|
|
|
|
|
|
def get_host_pubkey_fn(args, fqdn):
|
|
fns = [os.path.join(args.certdir, '{}_infra.pem'.format(fqdn)),
|
|
os.path.join(args.certdir, '{}.pem'.format(fqdn)),
|
|
os.path.join(args.certdir, 'public_certkey.pkcs7.pem'),
|
|
]
|
|
for fn in fns:
|
|
if os.path.isfile(fn):
|
|
return fn
|
|
print('Public key file not found (tried {})'.format(pubkey_fn, fns))
|
|
return None
|
|
|
|
|
|
def get_host_eyaml_fn(fqdn):
|
|
return '{}/overlay/etc/hiera/data/local.eyaml'.format(fqdn)
|
|
|
|
|
|
def get_host_secrets_fn(fqdn):
|
|
return '{}/overlay/etc/hiera/data/secrets.yaml.asc'.format(fqdn)
|
|
|
|
|
|
def get_host_secrets(fqdn):
|
|
"""
|
|
SSH to a host and read all it's secrets.
|
|
|
|
:param fqdn: Fully qualified hostname
|
|
:return: Two dicts (data, comments)
|
|
"""
|
|
data = run(['ssh',
|
|
fqdn,
|
|
'GNUPGHOME=/etc/hiera/gpg/ gpg -d /etc/hiera/data/secrets.yaml.asc 2>/dev/null'])
|
|
|
|
if not data['success']:
|
|
return False, False
|
|
|
|
return _parse_eyaml(data['output'].decode('utf-8'))
|
|
|
|
|
|
def load_host_eyaml(fn):
|
|
"""
|
|
Load an eyaml file and return parsed data plus comments
|
|
|
|
:param fn: EYAML filename
|
|
:return: Two dicts (data, comments)
|
|
"""
|
|
if not os.path.isfile(fn):
|
|
return {}, {}
|
|
|
|
with open(fn) as fd:
|
|
data_str = fd.read(1024 * 1024)
|
|
return _parse_eyaml(data_str)
|
|
|
|
|
|
def _parse_eyaml(data):
|
|
"""
|
|
Parse YAML data but also retain comments associated with the keys.
|
|
|
|
:param data:
|
|
:return: Two dicts (data, comments)
|
|
"""
|
|
if not data:
|
|
return False, False
|
|
try:
|
|
comments = extract_comments(data)
|
|
return yaml.safe_load(data), comments
|
|
except Exception as exc:
|
|
sys.stderr.write('Failed to parse EYAML:\n{}\n{}\n'.format(data, exc))
|
|
|
|
return False, False
|
|
|
|
|
|
def make_eyaml(data, comments, pubkey_fn):
|
|
res = []
|
|
for k, v in sorted(data.items()):
|
|
cmd = ['eyaml',
|
|
'encrypt',
|
|
'--label', k,
|
|
'--output', 'block',
|
|
'--stdin',
|
|
'--pkcs7-public-key', pubkey_fn,
|
|
]
|
|
eyaml = run(cmd, input=bytes(str(v), 'utf-8'))
|
|
if not eyaml['success']:
|
|
return False
|
|
if k in comments:
|
|
res += comments[k]
|
|
enc = eyaml['output'].decode('utf-8')
|
|
if 'PKCS' not in enc:
|
|
print("Command '{}' failed: {}".format(' '.join(cmd), enc))
|
|
res += enc.split('\n')
|
|
return res
|