2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-08-27 05:30:53 +00:00
Files
confluent/confluent/config.py
2013-08-16 16:37:19 -04:00

256 lines
9.0 KiB
Python

# Copyright 2013 IBM
# All rights reserved
# Note on the cryptography. Default behavior is mostly just to pave the
# way to meaningful security. Root all potentially sensitive data in
# one key. That key is in plain sight, so not meaningfully protected
# However, the key can be protected in the following ways:
# - Passphrase protected (requiring human interaction every restart)
# - TPM sealing (which would forgo the interactive assuming risk of
# physical attack on TPM is not a concern)
# This time around, expression based values will be parsed when set, and the
# parsing results will be stored rather than parsing on every evaluation
# Additionally, the option will be made available to use other attributes
# as well as the $1, $2, etc extracted from nodename. Left hand side can
# be requested to customize $1 and $2, but it is not required
#Actually, may override one of the python string formatters:
# 2.6 String.Formatter, e.g. "hello {world}"
# 2.4 string.Template, e.g. "hello $world"
# In JSON mode, will just read and write entire thing, with a comment
# to dissuade people from hand editing.
# In JSON mode, a file for different categories (site, nodes, etc)
# in redis, each category is a different database number
import array
import collections
import math
import os
from Crypto.Cipher import AES
from Crypto.Hash import HMAC
from Crypto.Hash import SHA25sterkey = None
_masterintegritykey = None
_cfgstore = {}
def _expand_expression(attribute, nodeobj):
# here is where we may avail ourselves of string.Formatter or
# string.Template
# we would then take the string that is identifier and do
# a little ast magic
# {(n1+1)/12+1} would take first number from nodename
# {enclosure.id * 8} would take enclosure.id value
# ast scheme would envolve the operator module and ast
# modules, with a mapping from ast operator classes to
# valid operator functions
# ast.parse gives a body array, and value is where we kick off
# ast.Num has an 'n' member to give the number
# ast.Attribute o
#>>> import ast
#>>> b=ast.parse("enclosure.id+n0+1/2")
#>>> b.body[0].value
#<_ast.BinOp object at 0x7ff449ff0090>
#>>> b.body[0].value.op
#<_ast.Add object at 0x7ff4500faf90>
#>>> b.body[0].value.left
#<_ast.BinOp object at 0x7ff449ff00d0>
#>>> b.body[0].value.left.op
#<_ast.Add object at 0x7ff4500faf90>
#>>> b.body[0].value.left.left
#<_ast.Attribute object at 0x7ff449ff0110>
#>>> b.body[0].value.left.left.value.id
#'enclosure'
#>>> b.body[0].value.left.right
#<_ast.Name object at 0x7ff449ff0190>
#>>> b.body[0].value.left.right.id
#'n0'
#>>> b.body[0].value.left.left.id
#Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
#AttributeError: 'Attribute' object has no attribute 'id'
#>>> b.body[0].value.left.left.attr
#'id'
#import ast
#import operator as op
# supported operators
#operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
# ast.Div: op.truediv, ast.Pow: op.pow, ast.BitXor: op.xor}
#ef eval_expr(expr):
# """
# >>> eval_expr('2^6')
# 4
# >>> eval_expr('2**6')
# 64
# >>> eval_expr('1 + 2*3**(4^5) / (6 + -7)')
# -5.0
# """
# return eval_(ast.parse(expr).body[0].value) # Module(body=[Expr(value=...)])
#ef eval_(node):
# if isinstance(node, ast.Num): # <number>
# return node.n
# elif isinstance(node, ast.operator): # <operator>
# return operators[type(node)]
# elif isinstance(node, ast.BinOp): # <left> <operator> <right>
# return eval_(node.op)(eval_(node.left), eval_(node.right))
# else:
# raise TypeError(node)
pass
def unlock_config_keys(passphrase=None):
_init_masterkey(passphrase)
def _pbkdf2(passphrase, salt, iterations, size):
blocks = int(math.ceil(size/32.0)) # Hardcoded to SHA256 behavior
retkey = ""
for block in xrange(blocks):
citerations = iterations
tsalt = salt + chr(block)
currval = HMAC.new(passphrase, tsalt, SHA256).digest()
currarray = array.array('L',currval)
while citerations > 1:
currval = HMAC.new(passphrase, currval).digest()
nextarray = array.array('L',currval)
for index in range(nextarray):
currarray[index] = currarray[index] ^ nextarray[index]
currval = currarray.tostring()
currarray = nextarray
citerations = citerations - 1
retkey += currval
return retkey[:size]
def _derive_keys(passphrase, salt):
tmpkey = _pbkdf2(passphrase, salt, 50000, 32)
finalkey = _pbkdf2(tmpkey, salt, 50000, 96)
return (finalkey[:32],finalkey[32:])
def _get_protected_key(keydict, passphrase):
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")
for pp in keydict['passphraseprotected']:
salt = pp[0]
privkey, integkey = _derive_keys(passphrase, 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:
salt = os.urandom(32)
privkey, integkey = _derive_keys(passphrase, salt)
cval = _crypt_value(key, key=privkey, integritykey=integkey)
return {"passphraseprotected": cval}
else:
return {"unencryptedvalue": key}
def _init_masterkey(passphrase=None):
if 'master_privacy_key' in _cfgstore['globals']:
_masterkey = _get_protected_key(
_cfgstore['globals']['master_privacy_key'],
passphrase=passphrase)
else:
_masterkey = os.urandom(32)
_cfgstore['globals']['master_privacy_key'] = _format_key(_masterkey,
passphrase=passphrase)
if 'master_integrity_key' in _cfgstore['globals']:
_masterintegritykey = _get_protected_key(
_cfgstore['globals']['master_integrity_key'],
passphrase=passphrase
)
else:
_masterintegritykey = os.urandom(64)
_cfgstore['globals']['master_integrity_key'] = _format_key(
_masterintegritykey,
passphrase=passphrase
)
def _decrypt_value(cryptvalue,
key=_masterkey,
integritykey=_masterintegritykey):
iv, cipherdata, hmac = cryptvalue
if _masterkey is None or _masterintegritykey is None:
_init_masterkey()
check_hmac = HMAC.new(_masterintegritykey, cryptvalue, SHA256).digest()
if hmac != check_hmac:
raise Exception("bad HMAC value on crypted value")
decrypter = AES.new(_masterkey, AES.MODE_CBC, iv)
value = decrypter.decrypt(cryptvalue)
padsize = ord(value[-1])
pad = value[-padsize:]
# Note that I cannot grasp what could be done with a subliminal
# channel in padding in this case, but check the padding anyway
for padbyte in pad:
if ord(padbyte) != padsize:
raise Exception("bad padding in encrypted value")
return value[0:-padsize]
def _crypt_value(value,
key=_masterkey,
integritykey=_masterintegritykey):
# encrypt given value
# PKCS7 is the padding scheme to employ, if no padded needed, pad with 16
# check HMAC prior to attempting decrypt
if key is None or integritykey is None:
_init_masterkey()
key=_masterkey
integritykey=_masterintegritykey
iv = os.urandom(16)
crypter = AES.new(key, ASE.MOD_CBC, iv)
neededpad = 16 - (len(value) % 16)
pad = chr(neededpad) * neededpad
value = value + pad
cryptval = crypter.encrypt(value)
hmac = HMAC.new(integritykey, cryptval, SHA256).digest()
return (iv, cryptval, hmac)
class NodeAttribs(object):
def __init__(self, nodes=[], attributes=[], tenant=0):
self._nodelist = collecitons.dequeue(nodes)
self._tenant = tenant
self._attributes=attributes
def __iter__(self):
return self
def next():
node = self._nodelist.popleft()
onodeobj = _cfgstore['node'][(self._tenant,node)]
nodeobj =
attriblist = []
#if there is a filter, delete irrelevant keys
if self._attributes.length > 0:
for attribute in nodeobj.keys():
if attribute not in self._attributes:
del nodeobj[attribute]
#now that attributes are filtered, seek out and evaluate expressions
for attribute in nodeobj.keys():
if ('value' not in nodeobj[attribute] and
'cryptvalue' in nodeobj[attribute]):
nodeobj[attribute]['value'] = _decrypt_value(
nodeobj[attribute]['cryptvalue'])
if ('value' not in nodeobj[attribute] and
'expression' in nodeobj[attribute]):
nodeobj[attribute]['value'] = _expand_expression(
attribute=attribute,
nodeobj=nodeobj)