From 27a8a190a08e80ddf165983497e87b550b79b6e6 Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Wed, 20 Feb 2013 18:01:56 +0100 Subject: [PATCH] RAPI: Add flag to require authentication MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit 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: Michael Hanselmann Reviewed-by: Guido Trotter --- NEWS | 10 ++++++++++ lib/rapi/testutils.py | 4 ++-- lib/server/rapi.py | 13 ++++++++++--- man/ganeti-rapi.rst | 13 +++++++------ test/py/ganeti.server.rapi_unittest.py | 22 ++++++++++++++-------- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/NEWS b/NEWS index 2a09b2f..65ac4d5 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,16 @@ News ==== +Version 2.8.0 beta1 +------------------- + +*(unreleased)* + +- The :doc:`Remote API ` 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 ------------------- diff --git a/lib/rapi/testutils.py b/lib/rapi/testutils.py index ccb8b63..cdc34dc 100644 --- a/lib/rapi/testutils.py +++ b/lib/rapi/testutils.py @@ -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. diff --git a/lib/server/rapi.py b/lib/server/rapi.py index 65162ee..47ebb5b 100644 --- a/lib/server/rapi.py +++ b/lib/server/rapi.py @@ -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 @staticmethod 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) SetupFileWatcher(pathutils.RAPI_USERS_FILE, @@ -360,6 +363,10 @@ def Main(): usage="%prog [-f] [-d] [-p port] [-b ADDRESS]", version="%%prog (ganeti) %s" % 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, default_ssl_cert=pathutils.RAPI_CERT_FILE, diff --git a/man/ganeti-rapi.rst b/man/ganeti-rapi.rst index ad78e85..7b5ce1f 100644 --- a/man/ganeti-rapi.rst +++ b/man/ganeti-rapi.rst @@ -9,8 +9,8 @@ ganeti-rapi - Ganeti remote API daemon Synopsis -------- -**ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*] [-C -*SSL_CERT_FILE*] +| **ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*] +| [-C *SSL_CERT_FILE*] [\--require-authentication] DESCRIPTION ----------- @@ -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. +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: diff --git a/test/py/ganeti.server.rapi_unittest.py b/test/py/ganeti.server.rapi_unittest.py index 735c300..bf73da9 100755 --- a/test/py/ganeti.server.rapi_unittest.py +++ b/test/py/ganeti.server.rapi_unittest.py @@ -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, + reqauth=False): + 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/inst1.example.com/console" 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) - else: - self.assertEqual(code, http.HttpNotImplemented.code) + if method == http.HTTP_GET or reqauth: + self.assertEqual(code, http.HttpUnauthorized.code) + else: + self.assertEqual(code, http.HttpNotImplemented.code) class _FakeLuxiClientForQuery: -- 1.7.10.4