2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-25 19:10:10 +00:00

Implement dump of cfg to JSON

Restoring data is not yet implemented, after restore backend implemented than a
utility can be furnished.
This commit is contained in:
Jarrod Johnson 2014-08-19 15:06:28 -04:00
parent a0ae523768
commit 6fc75d8394
3 changed files with 114 additions and 36 deletions

13
TODO
View File

@ -50,4 +50,15 @@ Traceback (most recent call last):
AttributeError: 'str' object has no attribute 'iterkeys'
-/detected/ collection
-SLP snooping: snoop for srvrequests, likel
-show power/state while session is believed to be good but will timeout
-Config backup/restore
-seal to password
-Scenario was somehow solconnection object is 'broken' (broken=True) but the supervising console manager
never notices. The 'write()' doesn't raise an exception, just returns.
-pyghmi should except on attempt to write to broken console I think...
-need to investigate what path does not end up getting immediate notification
for disconnection
-last observed when a switch between confluent and imm was resetting in a loop
while it was trying to keep console open as much as possibl
-cs._handled_consoles[('n4', None)]._console.handle_data({'error':1}) is how I was able to
kick the session state back to working order after whatever event that was missed above
occurred.

View File

@ -1 +1 @@
0.2.1
0.2.2

View File

@ -62,11 +62,13 @@ from Crypto.Hash import HMAC
from Crypto.Hash import SHA256
import anydbm as dbm
import ast
import base64
import confluent.config.attributes as allattributes
import confluent.util
import copy
import cPickle
import errno
import json
import operator
import os
import random
@ -79,7 +81,7 @@ import threading
_masterkey = None
_masterintegritykey = None
_dirtylock = threading.RLock()
_config_areas = ('nodegroups', 'nodes', 'usergroups', 'users')
def _mkpath(pathname):
try:
@ -91,62 +93,62 @@ def _mkpath(pathname):
raise
def _derive_keys(passphrase, salt):
def _derive_keys(password, salt):
#implement our specific combination of pbkdf2 transforms to get at
#key. We bump the iterations up because we can afford to
#TODO: WORKERPOOL PBKDF2 is expensive
tmpkey = KDF.PBKDF2(passphrase, salt, 32, 50000,
tmpkey = KDF.PBKDF2(password, salt, 32, 50000,
lambda p, s: HMAC.new(p, s, SHA256).digest())
finalkey = KDF.PBKDF2(tmpkey, salt, 32, 50000,
lambda p, s: HMAC.new(p, s, SHA256).digest())
return finalkey[:32], finalkey[32:]
def _get_protected_key(keydict, passphrase):
def _get_protected_key(keydict, password):
if keydict['unencryptedvalue']:
return keydict['unencryptedvalue']
# TODO(jbjohnso): check for TPM sealing
if 'passphraseprotected' in keydict:
if passphrase is None:
raise Exception("Passphrase protected secret requires passhrase")
if password is None:
raise Exception("Passphrase protected secret requires password")
for pp in keydict['passphraseprotected']:
salt = pp[0]
privkey, integkey = _derive_keys(passphrase, salt)
privkey, integkey = _derive_keys(password, salt)
return decrypt_value(pp[1:], key=privkey, integritykey=integkey)
else:
raise Exception("No available decryption key")
def _format_key(key, passphrase=None):
if passphrase is not None:
def _format_key(key, password=None):
if password is not None:
salt = os.urandom(32)
privkey, integkey = _derive_keys(passphrase, salt)
privkey, integkey = _derive_keys(password, salt)
cval = crypt_value(key, key=privkey, integritykey=integkey)
return {"passphraseprotected": cval}
else:
return {"unencryptedvalue": key}
def init_masterkey(passphrase=None):
def init_masterkey(password=None):
global _masterkey
global _masterintegritykey
cfgn = get_global('master_privacy_key')
if cfgn:
_masterkey = _get_protected_key(cfgn, passphrase=passphrase)
_masterkey = _get_protected_key(cfgn, password=password)
else:
_masterkey = os.urandom(32)
set_global('master_privacy_key', _format_key(
_masterkey,
passphrase=passphrase))
password=password))
cfgn = get_global('master_integrity_key')
if cfgn:
_masterintegritykey = _get_protected_key(cfgn, passphrase=passphrase)
_masterintegritykey = _get_protected_key(cfgn, password=password)
else:
_masterintegritykey = os.urandom(64)
set_global('master_integrity_key', _format_key(
_masterintegritykey,
passphrase=passphrase))
password=password))
def decrypt_value(cryptvalue,
@ -258,7 +260,7 @@ def _mark_dirtykey(category, key, tenant=None):
def _generate_new_id():
# generate a random id outside the usual ranges used for norml users in
# generate a random id outside the usual ranges used for normal users in
# /etc/passwd. Leave an equivalent amount of space near the end disused,
# just in case
uid = str(confluent.util.securerandomnumber(65537, 4294901759))
@ -560,7 +562,7 @@ class ConfigManager(object):
self._cfgstore['usergroups'][attribute] = attributemap[attribute]
_mark_dirtykey('usergroups', groupname, self.tenant)
def create_usergroup(selfself, groupname, role="Administrator"):
def create_usergroup(self, groupname, role="Administrator"):
if 'usergroups' not in self._cfgstore:
self._cfgstore['usergroups'] = {}
groupname = groupname.encode('utf-8')
@ -569,7 +571,6 @@ class ConfigManager(object):
self._cfgstore['usergroups'][groupname] = {'role': role}
_mark_dirtykey('usergroups', groupname, self.tenant)
def set_user(self, name, attributemap):
"""Set user attribute(s)
@ -1054,30 +1055,69 @@ class ConfigManager(object):
self._bg_sync_to_file()
#TODO: wait for synchronization to suceed/fail??)
def _dump_to_json(self, redact=None):
"""Dump the configuration in json form to output
password is used to protect the 'secret' attributes in liue of the
actual in-configuration master key (which will have no clear form
in the dump
:param redact: If True, then sensitive password data will be redacted.
Other values may be used one day to redact in more
complex and interesting ways for non-secret
data.
"""
dumpdata = {}
for confarea in _config_areas:
if confarea not in self._cfgstore:
continue
dumpdata[confarea] = {}
for element in self._cfgstore[confarea].iterkeys():
dumpdata[confarea][element] = \
copy.deepcopy(self._cfgstore[confarea][element])
for attribute in self._cfgstore[confarea][element].iterkeys():
if 'inheritedfrom' in dumpdata[confarea][element][attribute]:
del dumpdata[confarea][element][attribute]
elif (attribute == 'cryptpass' or
'cryptvalue' in
dumpdata[confarea][element][attribute]):
if redact is not None:
dumpdata[confarea][element][attribute] = '*REDACTED*'
else:
if attribute == 'cryptpass':
target = dumpdata[confarea][element][attribute]
else:
target = dumpdata[confarea][element][attribute]['cryptvalue']
cryptval = []
for value in target:
cryptval.append(base64.b64encode(value))
if attribute == 'cryptpass':
dumpdata[confarea][element][attribute] = '!'.join(cryptval)
else:
dumpdata[confarea][element][attribute]['cryptvalue'] = '!'.join(cryptval)
elif isinstance(dumpdata[confarea][element][attribute], set):
dumpdata[confarea][element][attribute] = \
list(dumpdata[confarea][element][attribute])
return json.dumps(
dumpdata, sort_keys=True, indent=4, separators=(',', ': '))
@classmethod
def _read_from_path(cls):
global _cfgstore
_cfgstore = {}
rootpath = cls._cfgdir
_load_dict_from_dbm(['globals'], rootpath + "/globals")
_load_dict_from_dbm(['main', 'nodes'], rootpath + "/nodes")
_load_dict_from_dbm(['main', 'users'], rootpath + "/users")
_load_dict_from_dbm(['main', 'nodegroups'], rootpath + "/nodegroups")
_load_dict_from_dbm(['main', 'usergroups'], rootpath + "/usergroups")
for confarea in _config_areas:
_load_dict_from_dbm(['main', confarea], rootpath + "/" + confarea)
try:
for tenant in os.listdir(rootpath + '/tenants/'):
_load_dict_from_dbm(
['main', tenant, 'nodes'],
"%s/%s/nodes" % (rootpath, tenant))
_load_dict_from_dbm(
['main', tenant, 'nodegroups'],
"%s/%s/groups" % (rootpath, tenant))
_load_dict_from_dbm(
['main', tenant, 'users'],
"%s/%s/users" % (rootpath, tenant))
_load_dict_from_dbm(
['main', tenant, 'usergroups'],
"%s/%s/usergroups" % (rootpath, tenant))
for confarea in _config_areas:
_load_dict_from_dbm(
['main', tenant, confarea],
"%s/%s/%s" % (rootpath, tenant, confarea))
except OSError:
pass
@ -1149,6 +1189,33 @@ class ConfigManager(object):
self._recalculate_expressions(cfgobj[key], formatter, node,
changeset)
def _dump_keys(password):
if _masterkey is None or _masterintegritykey is None:
init_masterkey()
cryptkey = _format_key(_masterkey, password=password)
cryptkey = '!'.join(map(base64.b64encode, cryptkey['passphraseprotected']))
integritykey = _format_key(_masterintegritykey, password=password)
integritykey = '!'.join(map(base64.b64encode, integritykey['passphraseprotected']))
return json.dumps({'cryptkey': cryptkey, 'integritykey': integritykey},
sort_keys=True, indent=4, separators=(',', ': '))
def dump_db_to_directory(location, password, redact=None):
with open(os.path.join(location, 'keys.json'), 'w') as cfgfile:
cfgfile.write(_dump_keys(password))
cfgfile.write('\n')
with open(os.path.join(location, 'main.json'), 'w') as cfgfile:
cfgfile.write(ConfigManager(tenant=None)._dump_to_json(redact=redact))
cfgfile.write('\n')
try:
for tenant in os.listdir(ConfigManager._cfgdir + '/tenants/'):
with open(os.path.join(location, tenant + '.json'), 'w') as cfgfile:
cfgfile.write(ConfigManager(tenant=tenant)._dump_to_json(
redact=redact))
cfgfile.write('\n')
except OSError:
pass
try:
ConfigManager._read_from_path()