Revision 27a8a190
b/NEWS | ||
---|---|---|
2 | 2 |
==== |
3 | 3 |
|
4 | 4 |
|
5 |
Version 2.8.0 beta1 |
|
6 |
------------------- |
|
7 |
|
|
8 |
*(unreleased)* |
|
9 |
|
|
10 |
- The :doc:`Remote API <rapi>` daemon now supports a command line flag |
|
11 |
to always require authentication, ``--require-authentication``. It can |
|
12 |
be specified in ``$sysconfdir/default/ganeti``. |
|
13 |
|
|
14 |
|
|
5 | 15 |
Version 2.7.0 beta1 |
6 | 16 |
------------------- |
7 | 17 |
|
b/lib/rapi/testutils.py | ||
---|---|---|
210 | 210 |
"""Mocking out the RAPI server parts. |
211 | 211 |
|
212 | 212 |
""" |
213 |
def __init__(self, user_fn, luxi_client): |
|
213 |
def __init__(self, user_fn, luxi_client, reqauth=False):
|
|
214 | 214 |
"""Initialize this class. |
215 | 215 |
|
216 | 216 |
@type user_fn: callable |
... | ... | |
219 | 219 |
|
220 | 220 |
""" |
221 | 221 |
self.handler = \ |
222 |
server.rapi.RemoteApiHandler(user_fn, _client_cls=luxi_client) |
|
222 |
server.rapi.RemoteApiHandler(user_fn, reqauth, _client_cls=luxi_client)
|
|
223 | 223 |
|
224 | 224 |
def FetchResponse(self, path, method, headers, request_body): |
225 | 225 |
"""This is a callback method used to fetch a response. |
b/lib/server/rapi.py | ||
---|---|---|
73 | 73 |
""" |
74 | 74 |
AUTH_REALM = "Ganeti Remote API" |
75 | 75 |
|
76 |
def __init__(self, user_fn, _client_cls=None): |
|
76 |
def __init__(self, user_fn, reqauth, _client_cls=None):
|
|
77 | 77 |
"""Initializes this class. |
78 | 78 |
|
79 | 79 |
@type user_fn: callable |
80 | 80 |
@param user_fn: Function receiving username as string and returning |
81 | 81 |
L{http.auth.PasswordFileUser} or C{None} if user is not found |
82 |
@type reqauth: bool |
|
83 |
@param reqauth: Whether to require authentication |
|
82 | 84 |
|
83 | 85 |
""" |
84 | 86 |
# pylint: disable=W0233 |
... | ... | |
88 | 90 |
self._client_cls = _client_cls |
89 | 91 |
self._resmap = connector.Mapper() |
90 | 92 |
self._user_fn = user_fn |
93 |
self._reqauth = reqauth |
|
91 | 94 |
|
92 | 95 |
@staticmethod |
93 | 96 |
def FormatErrorMessage(values): |
... | ... | |
143 | 146 |
"""Determine whether authentication is required. |
144 | 147 |
|
145 | 148 |
""" |
146 |
return bool(self._GetRequestContext(req).handler_access) |
|
149 |
return self._reqauth or bool(self._GetRequestContext(req).handler_access)
|
|
147 | 150 |
|
148 | 151 |
def Authenticate(self, req, username, password): |
149 | 152 |
"""Checks whether a user can access a resource. |
... | ... | |
324 | 327 |
|
325 | 328 |
users = RapiUsers() |
326 | 329 |
|
327 |
handler = RemoteApiHandler(users.Get) |
|
330 |
handler = RemoteApiHandler(users.Get, options.reqauth)
|
|
328 | 331 |
|
329 | 332 |
# Setup file watcher (it'll be driven by asyncore) |
330 | 333 |
SetupFileWatcher(pathutils.RAPI_USERS_FILE, |
... | ... | |
360 | 363 |
usage="%prog [-f] [-d] [-p port] [-b ADDRESS]", |
361 | 364 |
version="%%prog (ganeti) %s" % |
362 | 365 |
constants.RELEASE_VERSION) |
366 |
parser.add_option("--require-authentication", dest="reqauth", |
|
367 |
default=False, action="store_true", |
|
368 |
help=("Disable anonymous HTTP requests and require" |
|
369 |
" authentication")) |
|
363 | 370 |
|
364 | 371 |
daemon.GenericMain(constants.RAPI, parser, CheckRapi, PrepRapi, ExecRapi, |
365 | 372 |
default_ssl_cert=pathutils.RAPI_CERT_FILE, |
b/man/ganeti-rapi.rst | ||
---|---|---|
9 | 9 |
Synopsis |
10 | 10 |
-------- |
11 | 11 |
|
12 |
**ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*] [-C
|
|
13 |
*SSL_CERT_FILE*]
|
|
12 |
| **ganeti-rapi** [-d] [-f] [\--no-ssl] [-K *SSL_KEY_FILE*]
|
|
13 |
| [-C *SSL_CERT_FILE*] [\--require-authentication]
|
|
14 | 14 |
|
15 | 15 |
DESCRIPTION |
16 | 16 |
----------- |
... | ... | |
23 | 23 |
``--no-ssl`` option, or alternatively the certificate used can be |
24 | 24 |
changed via the ``-C`` option and the key via the ``-K`` option. |
25 | 25 |
|
26 |
The daemon will listen to the "ganeti-rapi" tcp port, as listed in the
|
|
26 |
The daemon will listen to the "ganeti-rapi" TCP port, as listed in the
|
|
27 | 27 |
system services database, or if not defined, to port 5080 by default. |
28 | 28 |
|
29 | 29 |
See the *Ganeti remote API* documentation for further information. |
... | ... | |
36 | 36 |
|
37 | 37 |
Most query operations are allowed without authentication. Only the |
38 | 38 |
modification operations require authentication, in the form of basic |
39 |
authentication. |
|
39 |
authentication. Specify the ``--require-authentication`` command line |
|
40 |
flag to always require authentication. |
|
40 | 41 |
|
41 | 42 |
The users and their rights are defined in the |
42 |
``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. Format of this file is
|
|
43 |
described in the Ganeti documentation (``rapi.html``). |
|
43 |
``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. The format of this file
|
|
44 |
is described in the Ganeti documentation (``rapi.html``).
|
|
44 | 45 |
|
45 | 46 |
.. vim: set textwidth=72 : |
46 | 47 |
.. Local Variables: |
b/test/py/ganeti.server.rapi_unittest.py | ||
---|---|---|
51 | 51 |
return None |
52 | 52 |
|
53 | 53 |
def _Test(self, method, path, headers, reqbody, |
54 |
user_fn=NotImplemented, luxi_client=NotImplemented): |
|
55 |
rm = rapi.testutils._RapiMock(user_fn, luxi_client) |
|
54 |
user_fn=NotImplemented, luxi_client=NotImplemented, |
|
55 |
reqauth=False): |
|
56 |
rm = rapi.testutils._RapiMock(user_fn, luxi_client, reqauth=reqauth) |
|
56 | 57 |
|
57 | 58 |
(resp_code, resp_headers, resp_body) = \ |
58 | 59 |
rm.FetchResponse(path, method, http.ParseHeaders(StringIO(headers)), |
... | ... | |
70 | 71 |
self.assertEqual(code, http.HTTP_OK) |
71 | 72 |
self.assertTrue(data is None) |
72 | 73 |
|
74 |
def testRootReqAuth(self): |
|
75 |
(code, _, _) = self._Test(http.HTTP_GET, "/", "", None, reqauth=True) |
|
76 |
self.assertEqual(code, http.HttpUnauthorized.code) |
|
77 |
|
|
73 | 78 |
def testVersion(self): |
74 | 79 |
(code, _, data) = self._Test(http.HTTP_GET, "/version", "", None) |
75 | 80 |
self.assertEqual(code, http.HTTP_OK) |
... | ... | |
235 | 240 |
path = "/2/instances/inst1.example.com/console" |
236 | 241 |
|
237 | 242 |
for method in rapi.baserlib._SUPPORTED_METHODS: |
238 |
# No authorization |
|
239 |
(code, _, _) = self._Test(method, path, "", "") |
|
243 |
for reqauth in [False, True]: |
|
244 |
# No authorization |
|
245 |
(code, _, _) = self._Test(method, path, "", "", reqauth=reqauth) |
|
240 | 246 |
|
241 |
if method == http.HTTP_GET:
|
|
242 |
self.assertEqual(code, http.HttpUnauthorized.code) |
|
243 |
else: |
|
244 |
self.assertEqual(code, http.HttpNotImplemented.code) |
|
247 |
if method == http.HTTP_GET or reqauth:
|
|
248 |
self.assertEqual(code, http.HttpUnauthorized.code)
|
|
249 |
else:
|
|
250 |
self.assertEqual(code, http.HttpNotImplemented.code)
|
|
245 | 251 |
|
246 | 252 |
|
247 | 253 |
class _FakeLuxiClientForQuery: |
Also available in: Unified diff