diff --git a/daemons/ganeti-rapi b/daemons/ganeti-rapi index b6c0f80d94723f4f80acd87cdad0f5ea5f06a577..4607b0d15b614e53bfc32f778efd701f565fd3d8 100755 --- a/daemons/ganeti-rapi +++ b/daemons/ganeti-rapi @@ -26,6 +26,7 @@ import logging import optparse import sys import os +import os.path import signal from ganeti import constants @@ -36,6 +37,7 @@ from ganeti import ssconf from ganeti import utils from ganeti.rapi import connector +import ganeti.http.auth import ganeti.http.server @@ -46,16 +48,27 @@ class RemoteApiRequestContext(object): def __init__(self): self.handler = None self.handler_fn = None + self.handler_access = None -class RemoteApiHttpServer(http.server.HttpServer): +class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication, + http.server.HttpServer): """REST Request Handler Class. """ + AUTH_REALM = "Ganeti Remote API" + def __init__(self, *args, **kwargs): http.server.HttpServer.__init__(self, *args, **kwargs) + http.auth.HttpServerRequestAuthentication.__init__(self) self._resmap = connector.Mapper() + # Load password file + if os.path.isfile(constants.RAPI_USERS_FILE): + self._users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE) + else: + self._users = None + def _GetRequestContext(self, req): """Returns the context for a request. @@ -74,10 +87,41 @@ class RemoteApiHttpServer(http.server.HttpServer): except AttributeError, err: raise http.HttpBadRequest() + ctx.handler_access = getattr(ctx.handler, "%s_ACCESS" % method, None) + + # Require permissions definition (usually in the base class) + if ctx.handler_access is None: + raise AssertionError("Permissions definition missing") + req.private = ctx return req.private + def Authenticate(self, req, username, password): + """Checks whether a user can access a resource. + + """ + ctx = self._GetRequestContext(req) + + # Check username and password + valid_user = False + if self._users: + user = self._users.get(username, None) + if user and user.password == password: + valid_user = True + + if not valid_user: + # Unknown user or password wrong + return False + + if (not ctx.handler_access or + set(user.options).intersection(ctx.handler_access)): + # Allow access + return True + + # Access forbidden + raise http.HttpForbidden() + def HandleRequest(self, req): """Handles a request. diff --git a/lib/constants.py b/lib/constants.py index 7dfa2ac41f271d8ab3a1047a7b17737bbd1e9343..9ae904414a46e2dc757f164b96fa16ddfce2c378 100644 --- a/lib/constants.py +++ b/lib/constants.py @@ -96,6 +96,7 @@ CLUSTER_CONF_FILE = DATA_DIR + "/config.data" SSL_CERT_FILE = DATA_DIR + "/server.pem" WATCHER_STATEFILE = DATA_DIR + "/watcher.data" SSH_KNOWN_HOSTS_FILE = DATA_DIR + "/known_hosts" +RAPI_USERS_FILE = DATA_DIR + "/rapi_users" QUEUE_DIR = DATA_DIR + "/queue" ETC_HOSTS = "/etc/hosts" DEFAULT_FILE_STORAGE_DIR = _autoconf.FILE_STORAGE_DIR diff --git a/lib/rapi/__init__.py b/lib/rapi/__init__.py index 15360b5831a14231849b63d31accd45d89e95e1d..fde366c8097358ea81b252e42442c75814036491 100644 --- a/lib/rapi/__init__.py +++ b/lib/rapi/__init__.py @@ -19,4 +19,4 @@ # 02110-1301, USA. -# empty file for package definition +RAPI_ACCESS_WRITE = "write" diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 58d68a165d94df5a78d3531260d7bcc2ffccec8d..1409476120f48389623beaf975e3b4bdba8e5c10 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -27,6 +27,7 @@ import ganeti.cli import ganeti.opcodes from ganeti import luxi +from ganeti import rapi def BuildUriList(ids, uri_format, uri_fields=("name", "uri")): @@ -149,6 +150,12 @@ class R_Generic(object): """Generic class for resources. """ + # Default permission requirements + GET_ACCESS = [] + PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE] + POST_ACCESS = [rapi.RAPI_ACCESS_WRITE] + DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE] + def __init__(self, items, queryargs, req): """Generic resource constructor.