From: Boris Ranto Date: Tue, 25 Apr 2017 01:10:04 +0000 (-0700) Subject: restful: Use keys instead of tokens+cephx X-Git-Tag: ses5-milestone6~9^2~47^2~12 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=cdd2079dbb9b4d26e895367e97fe8f2b3d1a1228;p=ceph.git restful: Use keys instead of tokens+cephx Signed-off-by: Boris Ranto --- diff --git a/src/pybind/mgr/restful/api.py b/src/pybind/mgr/restful/api.py index 9cceccb24c2c..3c09ad6a4275 100644 --- a/src/pybind/mgr/restful/api.py +++ b/src/pybind/mgr/restful/api.py @@ -39,14 +39,17 @@ def auth(f): username, password = b64decode(request.authorization[1]).split(':') - # Lookup the password-less tokens first - if username not in module.instance.tokens.values(): - # Check the ceph auth db - msg = module.instance.verify_user(username, password) - if msg: - response.status = 401 - response.headers['WWW-Authenticate'] = 'Basic realm="Login Required"' - return {'message': 'auth: No HTTP username/password'} + # Check that the username exists + if username not in module.instance.keys: + response.status = 401 + response.headers['WWW-Authenticate'] = 'Basic realm="Login Required"' + return {'message': 'auth: No such user'} + + # Check the password + if module.instance.keys[username] != password: + response.status = 401 + response.headers['WWW-Authenticate'] = 'Basic realm="Login Required"' + return {'message': 'auth: Incorrect password'} return f(*args, **kwargs) return decorated @@ -637,64 +640,7 @@ class Config(RestController): -class Auth(RestController): - @expose('json') - @catch - def get(self): - """ - Generate a brand new password-less login token for the user - Uses HTTP Basic Auth for authentication - """ - if not request.authorization: - return ( - {'message': 'auth: No HTTP username/password'}, - 401, - {'WWW-Authenticate': 'Basic realm="Login Required"'} - ) - - username, password = b64decode(request.authorization[1]).split(':') - # Do not create a new token for a username that is already a token - if username in module.instance.tokens.values(): - return { - 'token': username - } - - # Check the ceph auth db - msg = module.instance.verify_user(username, password) - if msg: - return ( - {'message': 'auth: ' + msg}, - 401, - {'WWW-Authenticate': 'Basic realm="Login Required"'} - ) - - # Create a password-less login token for the user - # This overwrites any old user tokens - return { - 'token': module.instance.set_token(username) - } - - - @expose('json') - @catch - @auth - def delete(self): - """ - Delete the password-less login token for the user - """ - - username, password = b64decode(request.authorization[1]).split(':') - - if module.instance.unset_token(username): - return {'success': 'auth: Token removed'} - - response.status = 500 - return {'message': 'auth: No token for the user'} - - - class Root(RestController): - auth = Auth() config = Config() crush = Crush() doc = Doc() diff --git a/src/pybind/mgr/restful/module.py b/src/pybind/mgr/restful/module.py index ddfff9715f40..ba99cefcbea6 100644 --- a/src/pybind/mgr/restful/module.py +++ b/src/pybind/mgr/restful/module.py @@ -3,6 +3,7 @@ A RESTful API for Ceph """ import json +import errno import inspect import StringIO import threading @@ -179,7 +180,25 @@ class CommandsRequest(object): class Module(MgrModule): - COMMANDS = [] + COMMANDS = [ + { + "cmd": "create_key " + "name=key_name,type=CephString", + "desc": "Create an API key with this name", + "perm": "rw" + }, + { + "cmd": "delete_key " + "name=key_name,type=CephString", + "desc": "Delete an API key with this name", + "perm": "rw" + }, + { + "cmd": "list_keys", + "desc": "List all API keys", + "perm": "rw" + }, + ] def __init__(self, *args, **kwargs): super(Module, self).__init__(*args, **kwargs) @@ -189,7 +208,7 @@ class Module(MgrModule): self.requests = [] self.requests_lock = threading.RLock() - self.tokens = {} + self.keys = {} self.disable_auth = False self.shutdown_key = str(uuid4()) @@ -205,8 +224,8 @@ class Module(MgrModule): def _serve(self): - # Load stored authentication tokens - self.tokens = self.get_config_json("tokens") or {} + # Load stored authentication keys + self.keys = self.get_config_json("keys") or {} jsonify._instance = jsonify.GenericJSON( sort_keys=True, @@ -279,6 +298,48 @@ class Module(MgrModule): self.log.debug("Unhandled notification type '%s'" % notify_type) + def handle_command(self, command): + self.log.warn("Handling command: '%s'" % str(command)) + if command['prefix'] == "create_key": + if command['key_name'] in self.keys: + return 0, self.keys[command['key_name']], "" + + else: + self.keys[command['key_name']] = str(uuid4()) + self.set_config_json('keys', self.keys) + + return ( + 0, + self.keys[command['key_name']], + "", + ) + + elif command['prefix'] == "delete_key": + if command['key_name'] in self.keys: + del self.keys[command['key_name']] + self.set_config_json('keys', self.keys) + + return ( + 0, + "", + "", + ) + + elif command['prefix'] == "list_keys": + return ( + 0, + json.dumps(self.get_config_json('keys'), indent=2), + "", + ) + + else: + return ( + -errno.EINVAL, + "", + "Command not found '{0}'".format(prefix) + ) + + def create_cert(self): # create a key pair pkey = crypto.PKey() @@ -445,56 +506,3 @@ class Module(MgrModule): self.send_command(result, json.dumps(command), 'seq') return result.wait() - - - def verify_user(self, username, password): - r, outb, outs = self.run_command({ - 'prefix': 'auth get', - 'entity': username, - }) - - if r != 0: - return 'No such user/key (%s, %s)' % (outb, outs) - - ## check the capabilities, we are looking for mon allow * - conf = ConfigParser.ConfigParser() - - # ConfigParser can't handle tabs, remove them - conf.readfp(StringIO.StringIO(outb.replace('\t', ''))) - - if not conf.has_section(username): - return 'Failed to parse the auth details' - - key = conf.get(username, 'key') - - if password != key: - return 'Invalid key' - - if not conf.has_option(username, 'caps mon'): - return 'No mon caps set' - - caps = conf.get(username, 'caps mon') - - if caps not in ['"allow *"', '"allow profile mgr"']: - return 'Insufficient mon caps set' - - # Returning '' means 'no objections' - return '' - - - def set_token(self, user): - self.tokens[user] = str(uuid4()) - - self.set_config_json('tokens', self.tokens) - - return self.tokens[user] - - - def unset_token(self, user): - if user not in self.tokens: - return False - - del self.tokens[user] - self.set_config_json('tokens', self.tokens) - - return True