2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-22 09:32:21 +00:00

Implement a first attempt at '/users/' to manage authorization and authentication

This commit is contained in:
Jarrod Johnson 2014-04-10 17:05:57 -04:00
parent 4ad8c6ff40
commit 1b7a18c108
6 changed files with 96 additions and 42 deletions

View File

@ -313,7 +313,7 @@ def setvalues(attribs):
else: # an actual resource
resource = attribs[0]
attribs = attribs[1:]
keydata = paramaterize_attribs(attribs)
keydata = parameterize_attribs(attribs)
if not keydata:
return
targpath = fullpath_target(resource)

View File

@ -92,26 +92,6 @@ def authorize(name, element, tenant=False, access='rw'):
return None
def set_user_passphrase(name, passphrase, tenant=None):
"""Set user passphrase
:param name: The unique shortname of the user
:param passphrase: The passphrase to set for given user
:param tenant: The tenant to which the user belongs.
"""
# TODO(jbjohnso): WORKERPOOL
# When worker pool implemented, hand off the
# PBKDF2 to a worker instead of blocking
user, tenant = _get_usertenant(name, tenant)
_passcache[(user, tenant)] = passphrase
salt = os.urandom(8)
crypted = kdf.PBKDF2(passphrase, salt, 32, 10000,
lambda p, s: hash.HMAC.new(p, s, hash.SHA256).digest()
)
cfm = configmanager.ConfigManager(tenant)
cfm.set_user(name, {'cryptpass': (salt, crypted)})
def check_user_passphrase(name, passphrase, element=None, tenant=False):
"""Check a a login name and passphrase for authenticity and authorization

View File

@ -61,6 +61,11 @@ nic = {
},
}
user = {
'passphrase': {
'description': 'The passphrase used to authenticate this user'
},
}
# 'node', which can be considered a 'system' or a 'vm'
node = {

View File

@ -98,6 +98,7 @@ def _mkpath(pathname):
def _derive_keys(passphrase, 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,
lambda p, s: HMAC.new(p, s, SHA256).digest())
finalkey = kdf.PBKDF2(tmpkey, salt, 32, 50000,
@ -246,7 +247,8 @@ def set_global(globalname, value):
def _mark_dirtykey(category, key, tenant=None):
key = key.encode('utf-8')
if type(key) in (str, unicode):
key = key.encode('utf-8')
with _dirtylock:
if 'dirtykeys' not in _cfgstore:
_cfgstore['dirtykeys'] = {}
@ -261,11 +263,11 @@ def _generate_new_id():
# generate a random id outside the usual ranges used for norml users in
# /etc/passwd. Leave an equivalent amount of space near the end disused,
# just in case
id = confluent.util.securerandomnumber(65537, 4294901759)
if 'idmap' not in _cfgstore:
id = str(confluent.util.securerandomnumber(65537, 4294901759))
if 'idmap' not in _cfgstore['main']:
return id
while id in _cfgstore['idmap']:
id = confluent.util.securerandomnumber(65537, 4294901759)
while id in _cfgstore['main']['idmap']:
id = str(confluent.util.securerandomnumber(65537, 4294901759))
return id
class _ExpressionFormat(string.Formatter):
@ -483,6 +485,9 @@ class ConfigManager(object):
del self._notifierids[watcher]
def list_users(self):
return self._cfgstore['users'].iterkeys()
def get_user(self, name):
"""Get user information from DB
@ -509,11 +514,21 @@ class ConfigManager(object):
user = self._cfgstore['users'][name]
_mark_dirtykey('users', name, self.tenant)
for attribute in attributemap:
user[attribute] = attributemap[attribute]
if attribute == 'passphrase':
salt = os.urandom(8)
#TODO: WORKERPOOL, offload password set to a worker
crypted = kdf.PBKDF2(
attributemap[attribute], salt, 32, 10000,
lambda p, s: HMAC.new(p, s, SHA256).digest()
)
user['cryptpass'] = (salt, crypted)
else:
user[attribute] = attributemap[attribute]
self._bg_sync_to_file()
def create_user(self, name,
role="Administrator", id=None, displayname=None):
role="Administrator", id=None, displayname=None,
attributemap=None):
"""Create a new user
:param name: The login name of the user
@ -526,7 +541,7 @@ class ConfigManager(object):
if id is None:
id = _generate_new_id()
else:
if id in _cfgstore['idmap']:
if id in _cfgstore['main']['idmap']:
raise Exception("Duplicate id requested")
if 'users' not in self._cfgstore:
self._cfgstore['users'] = { }
@ -537,13 +552,15 @@ class ConfigManager(object):
self._cfgstore['users'][name] = {'id': id}
if displayname is not None:
self._cfgstore['users'][name]['displayname'] = displayname
if 'idmap' not in _cfgstore:
_cfgstore['idmap'] = {}
if 'idmap' not in _cfgstore['main']:
_cfgstore['main']['idmap'] = {}
_mark_dirtykey('idmap', id)
_cfgstore['idmap'][id] = {
_cfgstore['main']['idmap'][id] = {
'tenant': self.tenant,
'username': name
}
if attributemap is not None:
self.set_user(name, attributemap)
self._bg_sync_to_file()
def is_node(self, node):
@ -555,7 +572,7 @@ class ConfigManager(object):
def get_groups(self):
return self._cfgstore['groups'].iterkeys()
def get_nodes(self):
def list_nodes(self):
return self._cfgstore['nodes'].iterkeys()
def get_nodegroup_attributes(self, nodegroup, attributes=[]):

View File

@ -164,7 +164,7 @@ class ChildCollection(LinkRelation):
def get_input_message(path, operation, inputdata, nodes=None):
if path[0] == 'power' and path[1] == 'state' and operation != 'retrieve':
return InputPowerMessage(path, nodes, inputdata)
elif path[0] == 'attributes' and operation != 'retrieve':
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
return InputAttributes(path, inputdata, nodes)
elif path == ['boot', 'nextdevice'] and operation != 'retrieve':
return InputBootDevice(path, nodes, inputdata)
@ -326,7 +326,7 @@ class PowerState(ConfluentChoiceMessage):
class Attributes(ConfluentMessage):
def __init__(self, node=None, kv=None, desc=''):
def __init__(self, name=None, kv=None, desc=''):
self.desc = desc
nkv = {}
for key in kv.iterkeys():
@ -334,11 +334,11 @@ class Attributes(ConfluentMessage):
nkv[key] = {'value': kv[key]}
else:
nkv[key] = kv[key]
if node is None:
if name is None:
self.kvpairs = nkv
else:
self.kvpairs = {
node: nkv
name: nkv
}

View File

@ -32,6 +32,7 @@
# functions. Console is special and just get's passed through
# see API.txt
import confluent.config.attributes as attrscheme
import confluent.interface.console as console
import confluent.exceptions as exc
import confluent.messages as msg
@ -73,7 +74,7 @@ def load_plugins():
pluginmap[plugin] = tmpmod
rootcollections = ['nodes/', 'groups/']
rootcollections = ['nodes/', 'groups/', 'users/']
class PluginRoute(object):
@ -117,15 +118,47 @@ nodegroupresources = {
}
def create_user(inputdata, configmanager):
try:
username = inputdata['name']
del inputdata['name']
except (KeyError, ValueError):
raise exc.InvalidArgumentException()
configmanager.create_user(username, attributemap=inputdata)
def update_user(name, attribmap, configmanager):
try:
configmanager.set_user(name, attribmap)
except ValueError:
raise exc.InvalidArgumentException()
def show_user(name, configmanager):
userobj = configmanager.get_user(name)
rv = {}
for attr in attrscheme.user.iterkeys():
rv[attr] = None
if attr == 'passphrase':
if 'cryptpass' in userobj:
rv['passphrase'] = {'cryptvalue': True}
yield msg.CryptedAttributes(kv={'passphrase': rv['passphrase']},
desc=attrscheme.user[attr]['description'])
else:
if attr in userobj:
rv[attr] = userobj[attr]
yield msg.Attributes(kv={attr: rv[attr]},
desc=attrscheme.user[attr]['description'])
def stripnode(iterablersp, node):
for i in iterablersp:
i.strip_node(node)
yield i
def iterate_collections(iterable):
def iterate_collections(iterable, forcecollection=True):
for coll in iterable:
if coll[-1] != '/':
if forcecollection and coll[-1] != '/':
coll = coll + '/'
yield msg.ChildCollection(coll, candelete=True)
@ -166,7 +199,7 @@ def enumerate_nodegroup_collection(collectionpath, configmanager):
def enumerate_node_collection(collectionpath, configmanager):
if collectionpath == ['nodes']: # it is just '/node/', need a list of nodes
return iterate_collections(configmanager.get_nodes())
return iterate_collections(configmanager.list_nodes())
node = collectionpath[1]
if not configmanager.is_node(node):
raise exc.NotFoundException("Invalid element requested")
@ -256,7 +289,7 @@ def handle_path(path, operation, configmanager, inputdata=None):
if operation == "create":
inputdata = msg.InputAttributes(pathcomponents, inputdata)
create_node(inputdata.attribs, configmanager)
return iterate_collections(configmanager.get_nodes())
return iterate_collections(configmanager.list_nodes())
if len(pathcomponents) == 2:
iscollection = True
if iscollection:
@ -296,5 +329,24 @@ def handle_path(path, operation, configmanager, inputdata=None):
return passvalue
else:
return stripnode(passvalue, node)
elif pathcomponents[0] == 'users':
try:
user = pathcomponents[1]
except IndexError: # it's just users/
if operation == 'create':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata)
create_user(inputdata.attribs, configmanager)
return iterate_collections(configmanager.list_users(), forcecollection=False)
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata)
if operation == 'retrieve':
return show_user(user, configmanager)
elif operation == 'delete':
delete_user(user, configmanager)
return show_user(user, configmanager)
elif operation == 'update':
update_user(user, inputdata.attribs, configmanager)
return show_user(user, configmanager)
else:
raise exc.NotFoundException()