``/var/lib/ganeti/rapi/users``) on startup. Changes to the file will be
read automatically.
-Each line consists of two or three fields separated by whitespace. The
-first two fields are for username and password. The third field is
-optional and can be used to specify per-user options. Currently,
-``write`` is the only option supported and enables the user to execute
-operations modifying the cluster. Lines starting with the hash sign
-(``#``) are treated as comments.
+Lines starting with the hash sign (``#``) are treated as comments. Each
+line consists of two or three fields separated by whitespace. The first
+two fields are for username and password. The third field is optional
+and can be used to specify per-user options (separated by comma without
+spaces). Available options:
+
+.. pyassert::
+
+ rapi.RAPI_ACCESS_ALL == set([
+ rapi.RAPI_ACCESS_WRITE,
+ rapi.RAPI_ACCESS_READ,
+ ])
+
+:pyeval:`rapi.RAPI_ACCESS_WRITE`
+ Enables the user to execute operations modifying the cluster. Implies
+ :pyeval:`rapi.RAPI_ACCESS_READ` access.
+:pyeval:`rapi.RAPI_ACCESS_READ`
+ Allow access to operations querying for information.
Passwords can either be written in clear text or as a hash. Clear text
passwords may not start with an opening brace (``{``) or they must be
# Hashed password for Jessica
jessica {HA1}7046452df2cbb530877058712cf17bd4 write
+ # Monitoring can query for values
+ monitoring {HA1}ec018ffe72b8e75bb4d508ed5b6d079c query
+
+ # A user who can query and write
+ superuser {HA1}ec018ffe72b8e75bb4d508ed5b6d079c query,write
+
.. [#pwhash] Using the MD5 hash of username, realm and password is
described in :rfc:`2617` ("HTTP Authentication"), sections 3.2.2.2
Request information for connecting to instance's console.
-Supports the following commands: ``GET``.
+.. pyassert::
+
+ not (hasattr(rlib2.R_2_instances_name_console, "PUT") or
+ hasattr(rlib2.R_2_instances_name_console, "POST") or
+ hasattr(rlib2.R_2_instances_name_console, "DELETE"))
+
+Supports the following commands: ``GET``. Requires authentication with
+one of the following options:
+:pyeval:`utils.CommaJoin(rlib2.R_2_instances_name_console.GET_ACCESS)`.
``GET``
~~~~~~~
:pyeval:`utils.CommaJoin(constants.QR_VIA_RAPI)`. See the :doc:`query2
design document <design-query2>` for more details.
-Supports the following commands: ``GET``, ``PUT``.
+.. pyassert::
+
+ (rlib2.R_2_query.GET_ACCESS == rlib2.R_2_query.PUT_ACCESS and
+ not (hasattr(rlib2.R_2_query, "POST") or
+ hasattr(rlib2.R_2_query, "DELETE")))
+
+Supports the following commands: ``GET``, ``PUT``. Requires
+authentication with one of the following options:
+:pyeval:`utils.CommaJoin(rlib2.R_2_query.GET_ACCESS)`.
``GET``
~~~~~~~
else:
return None
- def _LookupUserWithWrite(name):
- if name == username:
- return http.auth.PasswordFileUser(name, password, [
- rapi.RAPI_ACCESS_WRITE,
- ])
- else:
- return None
-
- for qr in constants.QR_VIA_RAPI:
- # The /2/query resource has somewhat special rules for authentication as
- # it can be used to retrieve critical information
- path = "/2/query/%s" % qr
-
- for method in rapi.baserlib._SUPPORTED_METHODS:
- # No authorization
- (code, _, _) = self._Test(method, path, "", "")
-
- if method in (http.HTTP_DELETE, http.HTTP_POST):
- self.assertEqual(code, http.HttpNotImplemented.code)
- continue
-
- self.assertEqual(code, http.HttpUnauthorized.code)
-
- # Incorrect user
- (code, _, _) = self._Test(method, path, header_fn(True), "",
- user_fn=self._LookupWrongUser)
- self.assertEqual(code, http.HttpUnauthorized.code)
-
- # User has no write access, but the password is correct
- (code, _, _) = self._Test(method, path, header_fn(True), "",
- user_fn=_LookupUserNoWrite)
- self.assertEqual(code, http.HttpForbidden.code)
-
- # Wrong password and no write access
- (code, _, _) = self._Test(method, path, header_fn(False), "",
- user_fn=_LookupUserNoWrite)
- self.assertEqual(code, http.HttpUnauthorized.code)
-
- # Wrong password with write access
- (code, _, _) = self._Test(method, path, header_fn(False), "",
- user_fn=_LookupUserWithWrite)
- self.assertEqual(code, http.HttpUnauthorized.code)
-
- # Prepare request information
- if method == http.HTTP_PUT:
- reqpath = path
- body = serializer.DumpJson({
- "fields": ["name"],
- })
- elif method == http.HTTP_GET:
- reqpath = "%s?fields=name" % path
- body = ""
+ for access in [rapi.RAPI_ACCESS_WRITE, rapi.RAPI_ACCESS_READ]:
+ def _LookupUserWithWrite(name):
+ if name == username:
+ return http.auth.PasswordFileUser(name, password, [
+ access,
+ ])
else:
- self.fail("Unknown method '%s'" % method)
-
- # User has write access, password is correct
- (code, _, data) = self._Test(method, reqpath, header_fn(True), body,
- user_fn=_LookupUserWithWrite,
- luxi_client=_FakeLuxiClientForQuery)
- self.assertEqual(code, http.HTTP_OK)
- self.assertTrue(objects.QueryResponse.FromDict(data))
+ return None
+
+ for qr in constants.QR_VIA_RAPI:
+ # The /2/query resource has somewhat special rules for authentication as
+ # it can be used to retrieve critical information
+ path = "/2/query/%s" % qr
+
+ for method in rapi.baserlib._SUPPORTED_METHODS:
+ # No authorization
+ (code, _, _) = self._Test(method, path, "", "")
+
+ if method in (http.HTTP_DELETE, http.HTTP_POST):
+ self.assertEqual(code, http.HttpNotImplemented.code)
+ continue
+
+ self.assertEqual(code, http.HttpUnauthorized.code)
+
+ # Incorrect user
+ (code, _, _) = self._Test(method, path, header_fn(True), "",
+ user_fn=self._LookupWrongUser)
+ self.assertEqual(code, http.HttpUnauthorized.code)
+
+ # User has no write access, but the password is correct
+ (code, _, _) = self._Test(method, path, header_fn(True), "",
+ user_fn=_LookupUserNoWrite)
+ self.assertEqual(code, http.HttpForbidden.code)
+
+ # Wrong password and no write access
+ (code, _, _) = self._Test(method, path, header_fn(False), "",
+ user_fn=_LookupUserNoWrite)
+ self.assertEqual(code, http.HttpUnauthorized.code)
+
+ # Wrong password with write access
+ (code, _, _) = self._Test(method, path, header_fn(False), "",
+ user_fn=_LookupUserWithWrite)
+ self.assertEqual(code, http.HttpUnauthorized.code)
+
+ # Prepare request information
+ if method == http.HTTP_PUT:
+ reqpath = path
+ body = serializer.DumpJson({
+ "fields": ["name"],
+ })
+ elif method == http.HTTP_GET:
+ reqpath = "%s?fields=name" % path
+ body = ""
+ else:
+ self.fail("Unknown method '%s'" % method)
+
+ # User has write access, password is correct
+ (code, _, data) = self._Test(method, reqpath, header_fn(True), body,
+ user_fn=_LookupUserWithWrite,
+ luxi_client=_FakeLuxiClientForQuery)
+ self.assertEqual(code, http.HTTP_OK)
+ self.assertTrue(objects.QueryResponse.FromDict(data))
+
+ def testConsole(self):
+ path = "/2/instances/inst1.example.com/console"
+
+ for method in rapi.baserlib._SUPPORTED_METHODS:
+ # No authorization
+ (code, _, _) = self._Test(method, path, "", "")
+
+ if method == http.HTTP_GET:
+ self.assertEqual(code, http.HttpUnauthorized.code)
+ else:
+ self.assertEqual(code, http.HttpNotImplemented.code)
class _FakeLuxiClientForQuery: