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