Commit 27a8a190 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

RAPI: Add flag to require authentication

Most RAPI resources do not require authentication for the “GET” method.
In some setups it can be desirable to always require authentication.
This patch adds a command line parameter to always require it.

Some unrelated minor typos in the “ganeti-rapi” man page are also fixed.
Signed-off-by: default avatarMichael Hanselmann <>
Reviewed-by: default avatarGuido Trotter <>
parent ea322c27
......@@ -2,6 +2,16 @@ News
Version 2.8.0 beta1
- The :doc:`Remote API <rapi>` daemon now supports a command line flag
to always require authentication, ``--require-authentication``. It can
be specified in ``$sysconfdir/default/ganeti``.
Version 2.7.0 beta1
......@@ -210,7 +210,7 @@ class _RapiMock:
"""Mocking out the RAPI server parts.
def __init__(self, user_fn, luxi_client):
def __init__(self, user_fn, luxi_client, reqauth=False):
"""Initialize this class.
@type user_fn: callable
......@@ -219,7 +219,7 @@ class _RapiMock:
self.handler = \
server.rapi.RemoteApiHandler(user_fn, _client_cls=luxi_client)
server.rapi.RemoteApiHandler(user_fn, reqauth, _client_cls=luxi_client)
def FetchResponse(self, path, method, headers, request_body):
"""This is a callback method used to fetch a response.
......@@ -73,12 +73,14 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
AUTH_REALM = "Ganeti Remote API"
def __init__(self, user_fn, _client_cls=None):
def __init__(self, user_fn, reqauth, _client_cls=None):
"""Initializes this class.
@type user_fn: callable
@param user_fn: Function receiving username as string and returning
L{http.auth.PasswordFileUser} or C{None} if user is not found
@type reqauth: bool
@param reqauth: Whether to require authentication
# pylint: disable=W0233
......@@ -88,6 +90,7 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
self._client_cls = _client_cls
self._resmap = connector.Mapper()
self._user_fn = user_fn
self._reqauth = reqauth
def FormatErrorMessage(values):
......@@ -143,7 +146,7 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
"""Determine whether authentication is required.
return bool(self._GetRequestContext(req).handler_access)
return self._reqauth or bool(self._GetRequestContext(req).handler_access)
def Authenticate(self, req, username, password):
"""Checks whether a user can access a resource.
......@@ -324,7 +327,7 @@ def PrepRapi(options, _):
users = RapiUsers()
handler = RemoteApiHandler(users.Get)
handler = RemoteApiHandler(users.Get, options.reqauth)
# Setup file watcher (it'll be driven by asyncore)
......@@ -360,6 +363,10 @@ def Main():
usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
version="%%prog (ganeti) %s" %
parser.add_option("--require-authentication", dest="reqauth",
default=False, action="store_true",
help=("Disable anonymous HTTP requests and require"
" authentication"))
daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi,
......@@ -9,8 +9,8 @@ ganeti-rapi - Ganeti remote API daemon
**ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*] [-C
| **ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*]
| [-C *SSL_CERT_FILE*] [\--require-authentication]
......@@ -23,7 +23,7 @@ uses SSL encryption. This can be disabled by passing the
``--no-ssl`` option, or alternatively the certificate used can be
changed via the ``-C`` option and the key via the ``-K`` option.
The daemon will listen to the "ganeti-rapi" tcp port, as listed in the
The daemon will listen to the "ganeti-rapi" TCP port, as listed in the
system services database, or if not defined, to port 5080 by default.
See the *Ganeti remote API* documentation for further information.
......@@ -36,11 +36,12 @@ ACCESS CONTROLS
Most query operations are allowed without authentication. Only the
modification operations require authentication, in the form of basic
authentication. Specify the ``--require-authentication`` command line
flag to always require authentication.
The users and their rights are defined in the
``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. Format of this file is
described in the Ganeti documentation (``rapi.html``).
``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. The format of this file
is described in the Ganeti documentation (``rapi.html``).
.. vim: set textwidth=72 :
.. Local Variables:
......@@ -51,8 +51,9 @@ class TestRemoteApiHandler(unittest.TestCase):
return None
def _Test(self, method, path, headers, reqbody,
user_fn=NotImplemented, luxi_client=NotImplemented):
rm = rapi.testutils._RapiMock(user_fn, luxi_client)
user_fn=NotImplemented, luxi_client=NotImplemented,
rm = rapi.testutils._RapiMock(user_fn, luxi_client, reqauth=reqauth)
(resp_code, resp_headers, resp_body) = \
rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)),
......@@ -70,6 +71,10 @@ class TestRemoteApiHandler(unittest.TestCase):
self.assertEqual(code, http.HTTP_OK)
self.assertTrue(data is None)
def testRootReqAuth(self):
(code, _, _) = self._Test(http.HTTP_GET, "/", "", None, reqauth=True)
self.assertEqual(code, http.HttpUnauthorized.code)
def testVersion(self):
(code, _, data) = self._Test(http.HTTP_GET, "/version", "", None)
self.assertEqual(code, http.HTTP_OK)
......@@ -235,13 +240,14 @@ class TestRemoteApiHandler(unittest.TestCase):
path = "/2/instances/"
for method in rapi.baserlib._SUPPORTED_METHODS:
# No authorization
(code, _, _) = self._Test(method, path, "", "")
for reqauth in [False, True]:
# No authorization
(code, _, _) = self._Test(method, path, "", "", reqauth=reqauth)
if method == http.HTTP_GET:
self.assertEqual(code, http.HttpUnauthorized.code)
self.assertEqual(code, http.HttpNotImplemented.code)
if method == http.HTTP_GET or reqauth:
self.assertEqual(code, http.HttpUnauthorized.code)
self.assertEqual(code, http.HttpNotImplemented.code)
class _FakeLuxiClientForQuery:
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