Skip to content
Snippets Groups Projects
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 <hansmi@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent ea322c27
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,16 @@ News ...@@ -2,6 +2,16 @@ News
==== ====
Version 2.8.0 beta1
-------------------
*(unreleased)*
- 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 Version 2.7.0 beta1
------------------- -------------------
......
...@@ -210,7 +210,7 @@ class _RapiMock: ...@@ -210,7 +210,7 @@ class _RapiMock:
"""Mocking out the RAPI server parts. """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. """Initialize this class.
@type user_fn: callable @type user_fn: callable
...@@ -219,7 +219,7 @@ class _RapiMock: ...@@ -219,7 +219,7 @@ class _RapiMock:
""" """
self.handler = \ 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): def FetchResponse(self, path, method, headers, request_body):
"""This is a callback method used to fetch a response. """This is a callback method used to fetch a response.
......
...@@ -73,12 +73,14 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication, ...@@ -73,12 +73,14 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
""" """
AUTH_REALM = "Ganeti Remote API" 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. """Initializes this class.
@type user_fn: callable @type user_fn: callable
@param user_fn: Function receiving username as string and returning @param user_fn: Function receiving username as string and returning
L{http.auth.PasswordFileUser} or C{None} if user is not found L{http.auth.PasswordFileUser} or C{None} if user is not found
@type reqauth: bool
@param reqauth: Whether to require authentication
""" """
# pylint: disable=W0233 # pylint: disable=W0233
...@@ -88,6 +90,7 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication, ...@@ -88,6 +90,7 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
self._client_cls = _client_cls self._client_cls = _client_cls
self._resmap = connector.Mapper() self._resmap = connector.Mapper()
self._user_fn = user_fn self._user_fn = user_fn
self._reqauth = reqauth
@staticmethod @staticmethod
def FormatErrorMessage(values): def FormatErrorMessage(values):
...@@ -143,7 +146,7 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication, ...@@ -143,7 +146,7 @@ class RemoteApiHandler(http.auth.HttpServerRequestAuthentication,
"""Determine whether authentication is required. """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): def Authenticate(self, req, username, password):
"""Checks whether a user can access a resource. """Checks whether a user can access a resource.
...@@ -324,7 +327,7 @@ def PrepRapi(options, _): ...@@ -324,7 +327,7 @@ def PrepRapi(options, _):
users = RapiUsers() users = RapiUsers()
handler = RemoteApiHandler(users.Get) handler = RemoteApiHandler(users.Get, options.reqauth)
# Setup file watcher (it'll be driven by asyncore) # Setup file watcher (it'll be driven by asyncore)
SetupFileWatcher(pathutils.RAPI_USERS_FILE, SetupFileWatcher(pathutils.RAPI_USERS_FILE,
...@@ -360,6 +363,10 @@ def Main(): ...@@ -360,6 +363,10 @@ def Main():
usage="%prog [-f] [-d] [-p port] [-b ADDRESS]", usage="%prog [-f] [-d] [-p port] [-b ADDRESS]",
version="%%prog (ganeti) %s" % version="%%prog (ganeti) %s" %
constants.RELEASE_VERSION) constants.RELEASE_VERSION)
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, daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi,
default_ssl_cert=pathutils.RAPI_CERT_FILE, default_ssl_cert=pathutils.RAPI_CERT_FILE,
......
...@@ -9,8 +9,8 @@ ganeti-rapi - Ganeti remote API daemon ...@@ -9,8 +9,8 @@ ganeti-rapi - Ganeti remote API daemon
Synopsis Synopsis
-------- --------
**ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*] [-C | **ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*]
*SSL_CERT_FILE*] | [-C *SSL_CERT_FILE*] [\--require-authentication]
DESCRIPTION DESCRIPTION
----------- -----------
...@@ -23,7 +23,7 @@ uses SSL encryption. This can be disabled by passing the ...@@ -23,7 +23,7 @@ uses SSL encryption. This can be disabled by passing the
``--no-ssl`` option, or alternatively the certificate used can be ``--no-ssl`` option, or alternatively the certificate used can be
changed via the ``-C`` option and the key via the ``-K`` option. 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. system services database, or if not defined, to port 5080 by default.
See the *Ganeti remote API* documentation for further information. See the *Ganeti remote API* documentation for further information.
...@@ -36,11 +36,12 @@ ACCESS CONTROLS ...@@ -36,11 +36,12 @@ ACCESS CONTROLS
Most query operations are allowed without authentication. Only the Most query operations are allowed without authentication. Only the
modification operations require authentication, in the form of basic modification operations require authentication, in the form of basic
authentication. authentication. Specify the ``--require-authentication`` command line
flag to always require authentication.
The users and their rights are defined in the The users and their rights are defined in the
``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. Format of this file is ``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. The format of this file
described in the Ganeti documentation (``rapi.html``). is described in the Ganeti documentation (``rapi.html``).
.. vim: set textwidth=72 : .. vim: set textwidth=72 :
.. Local Variables: .. Local Variables:
......
...@@ -51,8 +51,9 @@ class TestRemoteApiHandler(unittest.TestCase): ...@@ -51,8 +51,9 @@ class TestRemoteApiHandler(unittest.TestCase):
return None return None
def _Test(self, method, path, headers, reqbody, def _Test(self, method, path, headers, reqbody,
user_fn=NotImplemented, luxi_client=NotImplemented): user_fn=NotImplemented, luxi_client=NotImplemented,
rm = rapi.testutils._RapiMock(user_fn, luxi_client) reqauth=False):
rm = rapi.testutils._RapiMock(user_fn, luxi_client, reqauth=reqauth)
(resp_code, resp_headers, resp_body) = \ (resp_code, resp_headers, resp_body) = \
rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)), rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)),
...@@ -70,6 +71,10 @@ class TestRemoteApiHandler(unittest.TestCase): ...@@ -70,6 +71,10 @@ class TestRemoteApiHandler(unittest.TestCase):
self.assertEqual(code, http.HTTP_OK) self.assertEqual(code, http.HTTP_OK)
self.assertTrue(data is None) 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): def testVersion(self):
(code, _, data) = self._Test(http.HTTP_GET, "/version", "", None) (code, _, data) = self._Test(http.HTTP_GET, "/version", "", None)
self.assertEqual(code, http.HTTP_OK) self.assertEqual(code, http.HTTP_OK)
...@@ -235,13 +240,14 @@ class TestRemoteApiHandler(unittest.TestCase): ...@@ -235,13 +240,14 @@ class TestRemoteApiHandler(unittest.TestCase):
path = "/2/instances/inst1.example.com/console" path = "/2/instances/inst1.example.com/console"
for method in rapi.baserlib._SUPPORTED_METHODS: for method in rapi.baserlib._SUPPORTED_METHODS:
# No authorization for reqauth in [False, True]:
(code, _, _) = self._Test(method, path, "", "") # No authorization
(code, _, _) = self._Test(method, path, "", "", reqauth=reqauth)
if method == http.HTTP_GET: if method == http.HTTP_GET or reqauth:
self.assertEqual(code, http.HttpUnauthorized.code) self.assertEqual(code, http.HttpUnauthorized.code)
else: else:
self.assertEqual(code, http.HttpNotImplemented.code) self.assertEqual(code, http.HttpNotImplemented.code)
class _FakeLuxiClientForQuery: class _FakeLuxiClientForQuery:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment