From b5b67ef98e01506e65122619adc8851510b38090 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann <hansmi@google.com> Date: Fri, 19 Dec 2008 12:58:27 +0000 Subject: [PATCH] ganeti-rapi: Implement HTTP authentication Passwords are stored in "$localstatedir/lib/ganeti/rapi_users". User options specify the access permissions of a user (see docstring for ganeti.http.ReadPasswordFile), for which only "write" is supported to grant write access. Every other user has read-only access. Reviewed-by: amishchenko --- daemons/ganeti-rapi | 46 +++++++++++++++++++++++++++++++++++++++++++- lib/constants.py | 1 + lib/rapi/__init__.py | 2 +- lib/rapi/baserlib.py | 7 +++++++ 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/daemons/ganeti-rapi b/daemons/ganeti-rapi index b6c0f80d9..4607b0d15 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 7dfa2ac41..9ae904414 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 15360b583..fde366c80 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 58d68a165..140947612 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. -- GitLab