From 14a9220acbe6a1f53b386a78e36d4f122f930e12 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Mon, 6 Jul 2015 14:41:15 -0400 Subject: [PATCH] Enable support for IPMI user management Provide a framework for management of users on managed endpoints, and implement for IPMI plugin. From Juliana Motira --- confluent_server/confluent/core.py | 8 +- confluent_server/confluent/httpapi.py | 41 ++++++++-- confluent_server/confluent/messages.py | 80 ++++++++++++++++++- .../plugins/hardwaremanagement/ipmi.py | 35 ++++++++ 4 files changed, 155 insertions(+), 9 deletions(-) diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index f8ff23e2..7bd9850d 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -83,7 +83,7 @@ def load_plugins(): if plugtype == '.sh': pluginmap[plugin] = shellmodule.Plugin( os.path.join(plugindir, plugin + '.sh')) - elif "__init__" not in plugin: + elif "__init__" not in plugin: plugins.add(plugin) for plugin in plugins: tmpmod = __import__(plugin) @@ -126,7 +126,11 @@ noderesources = { 'pluginattrs': ['hardwaremanagement.method'], 'default': 'ipmi', }), - } + }, + 'users': PluginCollection({ + 'pluginattrs': ['hardwaremanagement.method'], + 'default': 'ipmi', + }), } }, '_console': { diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 9aea1ea6..0256113c 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -103,9 +103,36 @@ def node_creation_resources(): desc=attribs.node[attr]['description']).html() + '
\n' +def user_creation_resources(): + credential = { + 'uid': { + 'description': (''), + }, + 'username': { + 'description': (''), + }, + 'password': { + 'description': (''), + }, + 'privilege_level': { + 'description': (''), + }, + } + for attr in sorted(credential.iterkeys()): + if attr == "password": + yield confluent.messages.CryptedAttributes( + kv={attr: None}, + desc=credential[attr]['description']).html() + '
\n' + else: + yield confluent.messages.Attributes( + kv={attr: None}, + desc=credential[attr]['description']).html() + '
\n' + + create_resource_functions = { - '/nodes/': node_creation_resources, - '/groups/': group_creation_resources, + 'nodes': node_creation_resources, + 'groups': group_creation_resources, + 'users': user_creation_resources, } @@ -444,11 +471,15 @@ def _assemble_html(responses, resource, querydict, url, extension): if iscollection: # localpath = url[:-2] (why was this here??) try: + if url == '/users/': + return firstpass = True - for y in create_resource_functions[url](): + module = url.split('/') + if not module: + return + for y in create_resource_functions[module[-2]](): if firstpass: - yield "
Define new resource in %s:
" % \ - url.split("/")[-2] + yield "
Define new resource in %s:
" % module[-2] firstpass = False yield y yield ('' diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 1483120a..144244fa 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -107,7 +107,10 @@ class ConfluentMessage(object): for key in pairs.iterkeys(): val = pairs[key] value = self.defaultvalue - valtype = self.defaulttype + if isinstance(val, dict) and 'type' in val: + valtype = val['type'] + else: + valtype = self.defaulttype notes = [] if isinstance(val, list): @@ -322,6 +325,9 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False): return InputIdentifyMessage(path, nodes, inputdata) elif path == ['events', 'hardware', 'decode']: return InputAlertData(path, inputdata, nodes) + elif (path[:3] == ['configuration', 'management_controller', 'users'] and + operation != 'retrieve'): + return InputCredential(path, inputdata, nodes) elif inputdata: raise exc.InvalidArgumentException() @@ -405,6 +411,61 @@ class InputAttributes(ConfluentMessage): return nodeattr +class InputCredential(ConfluentMessage): + valid_privilege_levels = set([ + 'callback', + 'user', + 'operator', + 'administrator', + 'proprietary', + 'no_access', + ]) + + def __init__(self, path, inputdata, nodes=None): + self.credentials = {} + nestedmode = False + if not inputdata: + raise exc.InvalidArgumentException('no request data provided') + + if len(path) == 4: + inputdata['uid'] = path[-1] + + if ('uid' not in inputdata or 'privilege_level' not in inputdata + or 'username' not in inputdata or 'password' not in inputdata): + raise exc.InvalidArgumentException('missing arguments') + + if not inputdata['uid'].isdigit(): + raise exc.InvalidArgumentException('uid must be a number') + else: + inputdata['uid'] = int(inputdata['uid']) + if inputdata['privilege_level'] not in self.valid_privilege_levels: + raise exc.InvalidArgumentException('privilege_level is not one of ' + + ','.join(self.valid_privilege_levels)) + + if nodes is None: + raise exc.InvalidArgumentException( + 'This only supports per-node input') + for node in nodes: + self.credentials[node] = inputdata + + def get_attributes(self, node): + if node not in self.credentials: + return {} + credential = self.credentials[node] + for attr in credentials: + if type(credentials[attr]) in (str, unicode): + try: + # as above, use format() to see if string follows + # expression, store value back in case of escapes + tv = credential[attr].format() + credential[attr] = tv + except (KeyError, IndexError): + # an expression string will error if format() done + # use that as cue to put it into config as an expr + credential[attr] = {'expression': credential[attr]} + return credential + + class ConfluentInputMessage(ConfluentMessage): keyname = 'state' @@ -480,7 +541,7 @@ class BootDevice(ConfluentChoiceMessage): valid_paramset = { 'bootmode': valid_bootmodes, } - + def __init__(self, node, device, bootmode='unspecified'): if device not in self.valid_values: @@ -601,6 +662,21 @@ class EventCollection(ConfluentMessage): self.kvpairs = {name: {'events': eventdata}} +class User(ConfluentMessage): + def __init__(self, uid, username, privilege_level, name=None): + self.desc = 'foo' + self.stripped = False + self.notnode = name is None + kvpairs = {'username': {'value': username}, + 'password': {'value': '', 'type': 'password'}, + 'privilege_level': {'value': privilege_level} + } + if self.notnode: + self.kvpairs = kvpairs + else: + self.kvpairs = {name: kvpairs} + + class AlertDestination(ConfluentMessage): def __init__(self, ip, acknowledge=False, retries=0, name=None): self.desc = 'foo' diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index cdb02153..0c6fb43e 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -371,6 +371,8 @@ class IpmiHandler(object): def handle_configuration(self): if self.element[1:3] == ['management_controller', 'alerts' ]: return self.handle_alerts() + elif self.element[1:3] == ['management_controller', 'users' ]: + return self.handle_users() raise Exception('Not implemented') def decode_alert(self): @@ -421,6 +423,39 @@ class IpmiHandler(object): return raise Exception('Not implemented') + def handle_users(self): + if len(self.element) == 3: + if self.op == 'update': + user = self.inputdata.credentials[self.node] + self.ipmicmd.create_user(uid=user['uid'], name=user['username'], + password=user['password'], channel=1, + callback=True,link_auth=True, ipmi_msg=True, + privilege_level=user['privilege_level']) + # A list of users + for user in self.ipmicmd.get_users(channel=1): + self.output.put(msg.ChildCollection(user)) + return + elif len(self.element) == 4: + user = int(self.element[-1]) + if self.op == 'read': + data = self.ipmicmd.get_user(uid=user, channel=1) + self.output.put(msg.User( + uid=data['uid'], + username=data['name'], + privilege_level=data['access']['privilege_level'], + name=self.node)) + return + elif self.op == 'update': + user = self.inputdata.credentials[self.node] + self.ipmicmd.create_user(uid=user['uid'], name=user['username'], + password=user['password'], channel=1, + callback=True,link_auth=True, ipmi_msg=True, + privilege_level=user['privilege_level']) + return + elif self.op == 'delete': + self.ipmicmd.user_delete(uid=user, channel=1) + return + def do_eventlog(self): eventout = [] for event in self.ipmicmd.get_event_log():