Commit b5b67ef9 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

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
parent 7e9760c3
......@@ -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.
......
......@@ -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
......
......@@ -19,4 +19,4 @@
# 02110-1301, USA.
# empty file for package definition
RAPI_ACCESS_WRITE = "write"
......@@ -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.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment