In order to have more control over what users can use a virtual machine
under Kimchi, a VM should have users and groups associated with it.
Kimchi uses metadata information
(
http://libvirt.org/formatdomain.html#elementsMetadata) to store users
and groups to the virtual machines. The data is stored as string
representations of Python lists.
Keep in mind that these security restrictions will not apply when using
other virtual machine managers (e.g. virsh, virt-manager, etc).
Users and groups stored in the VM are now returned when the user queries the
VM data. Currently, there is no way to associate users and groups to a VM.
The REST command below now returns the users and groups of every
virtual machine:
/vms/:name GET: It now lists the users and groups associated with that VM.
{
"users":[
"user1",
"user2"
],
"groups":[
"group1",
"group2"
],
...
}
If there is no metadata information about users and groups, empty lists
will be displayed
Signed-off-by: Crístian Viana <vianac(a)linux.vnet.ibm.com>
---
docs/API.md | 4 ++++
src/kimchi/API.json | 22 ++++++++++++++++++++++
src/kimchi/control/vms.py | 4 +++-
src/kimchi/i18n.py | 4 ++++
src/kimchi/mockmodel.py | 4 +++-
src/kimchi/model/vms.py | 32 +++++++++++++++++++++++++++++++-
tests/test_mockmodel.py | 2 +-
tests/test_model.py | 4 +++-
tests/test_rest.py | 2 ++
9 files changed, 73 insertions(+), 5 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index e97eace..e79a38b 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -96,6 +96,10 @@ the following general conventions:
* port: The real port number of the graphics, vnc or spice. Users
can use this port to connect to the vm with general vnc/spice
clients.
+ * users: A list of system users who have permission to access the VM.
+ Default is: empty.
+ * groups: A list of system groups whose users have permission to access
+ the VM. Default is: empty.
* **DELETE**: Remove the Virtual Machine
* **PUT**: update the parameters of existed VM
* name: New name for this VM (only applied for shutoff VM)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index f595bbf..9d19351 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -183,6 +183,28 @@
"type": "string",
"minLength": 1,
"error": "KCHVM0011E"
+ },
+ "users": {
+ "description": "Array of users who have permission to
the VM",
+ "type": "array",
+ "uniqueItems": true,
+ "error": "KCHVM0020E",
+ "items": {
+ "description": "User name",
+ "type": "string",
+ "error": "KCHVM0021E"
+ }
+ },
+ "groups": {
+ "description": "Array of groups who have permission to
the VM",
+ "type": "array",
+ "uniqueItems": true,
+ "error": "KCHVM0022E",
+ "items": {
+ "description": "Group name",
+ "type": "string",
+ "error": "KCHVM0023E"
+ }
}
}
},
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a74ce27..89e9f45 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -58,7 +58,9 @@ class VM(Resource):
'icon': self.info['icon'],
'graphics': {'type':
self.info['graphics']['type'],
'listen':
self.info['graphics']['listen'],
- 'port':
self.info['graphics']['port']}
+ 'port':
self.info['graphics']['port']},
+ 'users': self.info['users'],
+ 'groups': self.info['groups']
}
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index ca72a80..179b38b 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -77,6 +77,10 @@ messages = {
"KCHVM0017E": _("Volume list (LUNs names) not given."),
"KCHVM0018E": _("Virtual machine volumes must be a list of strings
with distinct LUNs names."),
"KCHVM0019E": _("Unable to start virtual machine %(name)s. Details:
%(err)s"),
+ "KCHVM0020E": _("User names list must be an array"),
+ "KCHVM0021E": _("User name must be a string"),
+ "KCHVM0022E": _("Group names list must be an array"),
+ "KCHVM0023E": _("Group name must be a string"),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual
machine %(name)s"),
"KCHVMIF0002E": _("Network %(network)s specified for virtual machine
%(name)s does not exist"),
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index b23a024..5cdbc01 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -937,7 +937,9 @@ class MockVM(object):
'cpus': template_info['cpus'],
'icon': None,
'graphics': {'type': 'vnc',
'listen': '0.0.0.0',
- 'port': None}
+ 'port': None},
+ 'users': ['user1', 'user2',
'root'],
+ 'groups': ['group1', 'group2',
'admin']
}
self.info['graphics'].update(template_info['graphics'])
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index b6a42e6..dcb352c 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -20,6 +20,7 @@
# 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 ast
import os
import time
import uuid
@@ -27,6 +28,7 @@ from xml.etree import ElementTree
import libvirt
from cherrypy.process.plugins import BackgroundTask
+from lxml import etree
from kimchi import vnc
from kimchi import xmlutils
@@ -53,6 +55,17 @@ VM_LIVE_UPDATE_PARAMS = {}
stats = {}
+KIMCHI_NS_TAG = 'kimchi'
+KIMCHI_NS_URI = 'http://github.com/kimchi-project/kimchi'
+
+
+def _get_vm_metadata(xml, tag):
+ et = etree.fromstring(xml)
+ val = et.xpath("/domain/metadata/%s:access/%s:%s"
+ % (KIMCHI_NS_TAG, KIMCHI_NS_TAG, tag),
+ namespaces={KIMCHI_NS_TAG: KIMCHI_NS_URI})
+ return ast.literal_eval(val[0].text) if val else []
+
class VMsModel(object):
def __init__(self, **kargs):
@@ -305,6 +318,21 @@ class VMModel(object):
res['io_throughput'] = vm_stats.get('disk_io', 0)
res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
+ xml = dom.XMLDesc(0)
+ try:
+ users = _get_vm_metadata(xml, "users")
+ except:
+ # Wrong XML tree structure, wrong array syntax, or empty value:
+ # ignore error and build an empty list
+ users = []
+
+ try:
+ groups = _get_vm_metadata(xml, "groups")
+ except:
+ # Wrong XML tree structure, wrong array syntax, or empty value:
+ # ignore error and build an empty list
+ groups = []
+
return {'state': state,
'stats': res,
'uuid': dom.UUIDString(),
@@ -314,7 +342,9 @@ class VMModel(object):
'icon': icon,
'graphics': {"type": graphics_type,
"listen": graphics_listen,
- "port": graphics_port}
+ "port": graphics_port},
+ 'users': users,
+ 'groups': groups
}
def _vm_get_disk_paths(self, dom):
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py
index ab5eb59..863c740 100644
--- a/tests/test_mockmodel.py
+++ b/tests/test_mockmodel.py
@@ -138,7 +138,7 @@ class MockModelTests(unittest.TestCase):
self.assertEquals(u'test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory',
'cpus', 'screenshot',
- 'icon', 'graphics'))
+ 'icon', 'graphics', 'users',
'groups'))
stats_keys = set(('cpu_utilization',
'net_throughput', 'net_throughput_peak',
'io_throughput', 'io_throughput_peak'))
diff --git a/tests/test_model.py b/tests/test_model.py
index 74e2424..474da57 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -58,7 +58,7 @@ class ModelTests(unittest.TestCase):
self.assertEquals('test', vms[0])
keys = set(('state', 'stats', 'uuid', 'memory',
'cpus', 'screenshot',
- 'icon', 'graphics'))
+ 'icon', 'graphics', 'users',
'groups'))
stats_keys = set(('cpu_utilization',
'net_throughput', 'net_throughput_peak',
'io_throughput', 'io_throughput_peak'))
@@ -70,6 +70,8 @@ class ModelTests(unittest.TestCase):
self.assertEquals(None, info['icon'])
self.assertEquals(stats_keys, set(info['stats'].keys()))
self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm')
+ self.assertEquals([], info['users'])
+ self.assertEquals([], info['groups'])
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_vm_lifecycle(self):
diff --git a/tests/test_rest.py b/tests/test_rest.py
index ca96dc0..f8a87e6 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -182,6 +182,8 @@ class RestTests(unittest.TestCase):
vm = json.loads(self.request('/vms/vm-1').read())
self.assertEquals('vm-1', vm['name'])
self.assertEquals('shutoff', vm['state'])
+ self.assertEquals(['user1', 'user2', 'root'],
vm['users'])
+ self.assertEquals(['group1', 'group2', 'admin'],
vm['groups'])
def test_edit_vm(self):
req = json.dumps({'name': 'test', 'cdrom':
'/nonexistent.iso'})
--
1.8.5.3