Reviewed-by: Daniel Barboza <danielhb(a)linux.vnet.ibm.com>
On 02/18/2014 03:23 PM, Aline Manera wrote:
From: Aline Manera <alinefm(a)br.ibm.com>
Non-root users must have restricted access to Kimchi.
This patch block non-root urser to:
- get or create debug reports;
- reboot or shutdown host system;
- create, activate/deactivate or delete networks;
- create, activate/deactivate or delete storage pools;
- update or delete templates;
- create, start/stop/connect or delete vms.
It also updates the tests cases to always run as a root user.
And add authorization tests to make sure non-root users have restricted
access to kimchi.
Signed-off-by: Aline Manera <alinefm(a)br.ibm.com>
---
src/kimchi/control/debugreports.py | 2 +-
src/kimchi/control/host.py | 2 +-
src/kimchi/control/networks.py | 2 +-
src/kimchi/control/storagepools.py | 2 +-
src/kimchi/control/templates.py | 2 +-
src/kimchi/control/vms.py | 2 +-
tests/Makefile.am | 1 +
tests/test_authorization.py | 124 ++++++++++++++++++++++++++++++++++++
tests/utils.py | 23 ++++++-
9 files changed, 153 insertions(+), 7 deletions(-)
create mode 100644 tests/test_authorization.py
diff --git a/src/kimchi/control/debugreports.py b/src/kimchi/control/debugreports.py
index 324d826..57dc0f3 100644
--- a/src/kimchi/control/debugreports.py
+++ b/src/kimchi/control/debugreports.py
@@ -26,7 +26,7 @@ from kimchi.control.utils import internal_redirect
from kimchi.control.utils import UrlSubNode
-@UrlSubNode("debugreports", True)
+@UrlSubNode("debugreports", True, ['GET', 'POST'])
class DebugReports(AsyncCollection):
def __init__(self, model):
super(DebugReports, self).__init__(model)
diff --git a/src/kimchi/control/host.py b/src/kimchi/control/host.py
index 0852bd0..41e0040 100644
--- a/src/kimchi/control/host.py
+++ b/src/kimchi/control/host.py
@@ -31,7 +31,7 @@ from kimchi.exception import OperationFailed
from kimchi.template import render
-@UrlSubNode("host", True)
+@UrlSubNode("host", True, ['POST'])
class Host(Resource):
def __init__(self, model, id=None):
super(Host, self).__init__(model, id)
diff --git a/src/kimchi/control/networks.py b/src/kimchi/control/networks.py
index 8510e49..3a02f60 100644
--- a/src/kimchi/control/networks.py
+++ b/src/kimchi/control/networks.py
@@ -25,7 +25,7 @@ from kimchi.control.base import Collection, Resource
from kimchi.control.utils import UrlSubNode
-@UrlSubNode("networks", True)
+@UrlSubNode("networks", True, ['POST', 'DELETE'])
class Networks(Collection):
def __init__(self, model):
super(Networks, self).__init__(model)
diff --git a/src/kimchi/control/storagepools.py b/src/kimchi/control/storagepools.py
index ea19609..7e6bdd7 100644
--- a/src/kimchi/control/storagepools.py
+++ b/src/kimchi/control/storagepools.py
@@ -34,7 +34,7 @@ from kimchi.model.storagepools import ISO_POOL_NAME
from kimchi.control.utils import UrlSubNode
-@UrlSubNode("storagepools", True)
+@UrlSubNode("storagepools", True, ['POST', 'DELETE'])
class StoragePools(Collection):
def __init__(self, model):
super(StoragePools, self).__init__(model)
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py
index 58dafcc..8135e32 100644
--- a/src/kimchi/control/templates.py
+++ b/src/kimchi/control/templates.py
@@ -25,7 +25,7 @@ from kimchi.control.base import Collection, Resource
from kimchi.control.utils import UrlSubNode
-@UrlSubNode("templates", True)
+@UrlSubNode("templates", True, ['PUT', 'DELETE'])
class Templates(Collection):
def __init__(self, model):
super(Templates, self).__init__(model)
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index 60fc8ff..a74ce27 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -27,7 +27,7 @@ from kimchi.control.utils import internal_redirect, UrlSubNode
from kimchi.control.vm import sub_nodes
-@UrlSubNode("vms", True)
+@UrlSubNode("vms", True, ['POST', 'PUT', 'DELETE'])
class VMs(Collection):
def __init__(self, model):
super(VMs, self).__init__(model)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 0487ffc..e8db05c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -24,6 +24,7 @@ EXTRA_DIST = \
Makefile.am \
run_tests.sh.in \
iso_gen.py \
+ test_authorization.py \
test_config.py.in \
test_exception.py \
test_mockmodel.py \
diff --git a/tests/test_authorization.py b/tests/test_authorization.py
new file mode 100644
index 0000000..7f939a8
--- /dev/null
+++ b/tests/test_authorization.py
@@ -0,0 +1,124 @@
+#
+# Project Kimchi
+#
+# Copyright IBM, Corp. 2013
+#
+# Authors:
+# Aline Manera <alinefm(a)linux.vnet.ibm.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+import json
+import os
+import unittest
+
+
+from functools import partial
+
+
+import kimchi.mockmodel
+from utils import get_free_port, patch_auth, request
+from utils import run_server
+
+
+test_server = None
+model = None
+host = None
+port = None
+ssl_port = None
+
+
+def setUpModule():
+ global test_server, model, host, port, ssl_port
+
+ patch_auth(sudo = False)
+ model = kimchi.mockmodel.MockModel('/tmp/obj-store-test')
+ host = '127.0.0.1'
+ port = get_free_port('http')
+ test_server = run_server(host, port, None, test_mode=True, model=model)
+
+
+def tearDownModule():
+ test_server.stop()
+ os.unlink('/tmp/obj-store-test')
+
+
+class AuthorizationTests(unittest.TestCase):
+ def setUp(self):
+ self.request = partial(request, host, port)
+ model.reset()
+
+ def test_nonroot_access(self):
+ # Non-root users can access static host information
+ resp = self.request('/host', '{}', 'GET')
+ self.assertEquals(200, resp.status)
+
+ # Non-root users can access host stats
+ resp = self.request('/host/stats', '{}', 'GET')
+ self.assertEquals(200, resp.status)
+
+ # Non-root users can not reboot/shutdown host system
+ resp = self.request('/host/reboot', '{}', 'POST')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/host/shutdown', '{}', 'POST')
+ self.assertEquals(401, resp.status)
+
+ # Non-root users can not get or debug reports
+ resp = self.request('/debugreports', '{}', 'GET')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/debugreports', '{}', 'POST')
+ self.assertEquals(401, resp.status)
+
+ # Non-root users can not create or delete network (only get)
+ resp = self.request('/networks', '{}', 'GET')
+ self.assertEquals(200, resp.status)
+ resp = self.request('/networks', '{}', 'POST')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/networks/default/activate', '{}',
'POST')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/networks/default', '{}',
'DELETE')
+ self.assertEquals(401, resp.status)
+
+ # Non-root users can not create or delete storage pool (only get)
+ resp = self.request('/storagepools', '{}', 'GET')
+ self.assertEquals(200, resp.status)
+ resp = self.request('/storagepools', '{}', 'POST')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/storagepools/default/activate', '{}',
'POST')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/storagepools/default', '{}',
'DELETE')
+ self.assertEquals(401, resp.status)
+
+ # Non-root users can not update or delete a template
+ # but he can get and create a new one
+ resp = self.request('/templates', '{}', 'GET')
+ self.assertEquals(200, resp.status)
+ req = json.dumps({'name': 'test', 'cdrom':
'/nonexistent.iso'})
+ resp = self.request('/templates', req, 'POST')
+ self.assertEquals(201, resp.status)
+ resp = self.request('/templates/test', '{}', 'PUT')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/templates/test', '{}', 'DELETE')
+ self.assertEquals(401, resp.status)
+
+ # Non-root users can only get vms
+ resp = self.request('/vms', '{}', 'GET')
+ self.assertEquals(200, resp.status)
+ resp = self.request('/vms', req, 'POST')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/vms', '{}', 'PUT')
+ self.assertEquals(401, resp.status)
+ resp = self.request('/vms', '{}', 'DELETE')
+ self.assertEquals(401, resp.status)
diff --git a/tests/utils.py b/tests/utils.py
index 14c57d4..18b707c 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -147,11 +147,31 @@ def https_request(host, port, path, data=None,
method='GET', headers=None):
return _request(conn, path, data, method, headers)
-def patch_auth():
+def patch_auth(sudo=True):
"""
Override the authenticate function with a simple test against an
internal dict of users and passwords.
"""
+ USER_ID = 'userid'
+ USER_GROUPS = 'groups'
+ USER_SUDO = 'sudo'
+
+ class _User(object):
+ def __init__(self, userid):
+ self.user = {}
+ self.user[USER_ID] = userid
+ self.user[USER_GROUPS] = None
+ self.user[USER_SUDO] = sudo
+
+ def get_groups(self):
+ return self.user[USER_GROUPS]
+
+ def has_sudo(self):
+ return self.user[USER_SUDO]
+
+ def get_user(self):
+ return self.user
+
def _authenticate(username, password, service="passwd"):
try:
return fake_user[username] == password
@@ -161,6 +181,7 @@ def patch_auth():
import kimchi.auth
kimchi.auth.authenticate = _authenticate
+ kimchi.auth.User = _User
def normalize_xml(xml_str):