Change in vdsm[master]: iscsicred: Support per-host iscsi credentials database

nsoffer at redhat.com nsoffer at redhat.com
Thu Jul 2 23:21:02 UTC 2015


Nir Soffer has uploaded a new change for review.

Change subject: iscsicred: Support per-host iscsi credentials database
......................................................................

iscsicred: Support per-host iscsi credentials database

Some users need unique host iSCSI credentials for each target, but
Engine supports only same credentials for all hosts. This series adds
support for simple per-host iSCSI credentials database.

Credentials are stored in /etc/vdsm/iscsi-cred/<target-name>

Credentials file format:

    username = foo:bar
    password = 12345678

This patch adds the iscsicred module, providing readonly access to
the iSCSI credentials database.

Change-Id: I8f6a838f6b8e132d6b0c1a8135f2c28ef1e7f847
Signed-off-by: Nir Soffer <nsoffer at redhat.com>
---
M debian/vdsm.install
M tests/Makefile.am
A tests/iscsicred_test.py
M vdsm.spec.in
M vdsm/storage/Makefile.am
A vdsm/storage/iscsicred.py
M vdsm/supervdsmServer
7 files changed, 207 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/79/43179/1

diff --git a/debian/vdsm.install b/debian/vdsm.install
index 35fcba7..c7cc90e 100644
--- a/debian/vdsm.install
+++ b/debian/vdsm.install
@@ -110,6 +110,7 @@
 ./usr/share/vdsm/storage/imageSharing.py
 ./usr/share/vdsm/storage/iscsi.py
 ./usr/share/vdsm/storage/iscsiadm.py
+./usr/share/vdsm/storage/iscsicred.py
 ./usr/share/vdsm/storage/localFsSD.py
 ./usr/share/vdsm/storage/lvm.env
 ./usr/share/vdsm/storage/lvm.py
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b9b8e72..30ee221 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -56,6 +56,7 @@
 	iproute2Tests.py \
 	ipwrapperTests.py \
 	iscsiTests.py \
+	iscsicred_test.py \
 	jsonRpcHelper.py \
 	jsonRpcTests.py \
 	libvirtconnectionTests.py \
diff --git a/tests/iscsicred_test.py b/tests/iscsicred_test.py
new file mode 100644
index 0000000..715dded
--- /dev/null
+++ b/tests/iscsicred_test.py
@@ -0,0 +1,121 @@
+#
+# Copyright 2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+from contextlib import contextmanager
+import os
+
+from testlib import VdsmTestCase
+from testlib import namedTemporaryDir
+from monkeypatch import MonkeyPatchScope, MonkeyClass
+
+from vdsm.password import ProtectedPassword
+from storage import iscsicred
+
+
+class FakeSupervdsm(object):
+
+    def getProxy(self):
+        return self
+
+    def readTargetCredfile(self, targetName):
+        return iscsicred._read_credfile(targetName)
+
+
+ at MonkeyClass(iscsicred, "supervdsm", FakeSupervdsm())
+class IscsicredTests(VdsmTestCase):
+
+    TARGET = "iqn.1994-05.com.redhat:target7"
+
+    def test_not_found(self):
+        self.assertRaises(iscsicred.NotFound, iscsicred.get_credentials,
+                          self.TARGET)
+
+    def test_simple(self):
+        data = "".join(["username=foo:bar\n",
+                        "password=12345678\n"])
+        with credfile(self.TARGET, data):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {"username": "foo:bar",
+                             "password": ProtectedPassword("12345678")})
+
+    def test_whitespace(self):
+        data = "".join(["    username  =  foo:bar   \n",
+                        "    password  =  12345678  \n"])
+        with credfile(self.TARGET, data):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {"username": "foo:bar",
+                             "password": ProtectedPassword("12345678")})
+
+    def test_any_whitespace(self):
+        data = "".join(["\t\tusername\t\t=\t\tfoo:bar\t\t\n",
+                        "\t\tpassword\t\t=\t\t12345678\t\t\n"])
+        with credfile(self.TARGET, data):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {"username": "foo:bar",
+                             "password": ProtectedPassword("12345678")})
+
+    def test_empty_lines(self):
+        data = "".join(["\n",
+                        "username=foo:bar\n",
+                        "\n",
+                        "password=12345678\n",
+                        "\n"])
+        with credfile(self.TARGET, data):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {"username": "foo:bar",
+                             "password": ProtectedPassword("12345678")})
+
+    def test_no_eol(self):
+        data = "".join(["username=foo:bar\n",
+                        "password=12345678"])
+        with credfile(self.TARGET, data):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {"username": "foo:bar",
+                             "password": ProtectedPassword("12345678")})
+
+    def test_extra_key(self):
+        data = "".join(["username=foo:bar\n",
+                        "password=12345678\n"
+                        "extra=value\n"])
+        with credfile(self.TARGET, data):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {"username": "foo:bar",
+                             "password": ProtectedPassword("12345678"),
+                             "extra": "value"})
+
+    def test_empty(self):
+        with credfile(self.TARGET, ""):
+            d = iscsicred.get_credentials(self.TARGET)
+        self.assertEqual(d, {})
+
+    def test_invalid(self):
+        with credfile(self.TARGET, "invalid line\n"):
+            with self.assertRaises(iscsicred.InvalidCredfile):
+                iscsicred.get_credentials(self.TARGET)
+
+
+ at contextmanager
+def credfile(target, data):
+    with namedTemporaryDir() as tmpdir:
+        with MonkeyPatchScope([(iscsicred, "DIR", tmpdir)]):
+            filename = os.path.join(tmpdir, target)
+            with open(filename, "wb") as f:
+                f.write(data)
+            yield
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 58bf5ef..844a041 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -976,6 +976,7 @@
 %{_datadir}/%{vdsm_name}/storage/image.py*
 %{_datadir}/%{vdsm_name}/storage/imageSharing.py*
 %{_datadir}/%{vdsm_name}/storage/iscsiadm.py*
+%{_datadir}/%{vdsm_name}/storage/iscsicred.py*
 %{_datadir}/%{vdsm_name}/storage/iscsi.py*
 %{_datadir}/%{vdsm_name}/storage/localFsSD.py*
 %{_datadir}/%{vdsm_name}/storage/lvm.env
diff --git a/vdsm/storage/Makefile.am b/vdsm/storage/Makefile.am
index 4ebe0a2..5e64c44 100644
--- a/vdsm/storage/Makefile.am
+++ b/vdsm/storage/Makefile.am
@@ -42,6 +42,7 @@
 	image.py \
 	imageSharing.py \
 	iscsiadm.py \
+	iscsicred.py \
 	iscsi.py \
 	localFsSD.py \
 	lvm.py \
diff --git a/vdsm/storage/iscsicred.py b/vdsm/storage/iscsicred.py
new file mode 100644
index 0000000..7e6bfa3
--- /dev/null
+++ b/vdsm/storage/iscsicred.py
@@ -0,0 +1,76 @@
+#
+# Copyright 2015 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+"""
+iscsicred - access iscsi credetials database
+
+This module provides readonly access to per-host iscsi credentails.  Each host
+in a cluster may be configured with unique credentatils for each iscsi target.
+"""
+
+import errno
+import os
+from vdsm import password
+import supervdsm
+
+DIR = "/etc/vdsm/iscsi-cred"
+
+
+class NotFound(Exception):
+    """ Raised when getting non-existent cred file """
+
+
+class InvalidCredfile(Exception):
+    """ Raised if cred file cannot be parsed """
+
+
+def get_credentials(target_name):
+    data = supervdsm.getProxy().readTargetCredfile(target_name)
+    return _parse_credfile(data)
+
+
+def _read_credfile(target_name):
+    """
+    Read /etc/vdsm/iscsi-cred/target_name.
+
+    Called from supervdsm since credfiles are root-readonly files.
+    """
+    filename = os.path.join(DIR, target_name)
+    try:
+        with open(filename, "rb") as f:
+            return f.read()
+    except IOError as e:
+        if e.errno != errno.ENOENT:
+            raise
+        raise NotFound(target_name)
+
+
+def _parse_credfile(data):
+    cred = {}
+    for line in data.splitlines():
+        line = line.rstrip()
+        if not line:
+            continue
+        if "=" not in line:
+            raise InvalidCredfile("Invalid line: %r" % line)
+        key, value = [s.strip() for s in line.split("=", 1)]
+        if key == "password":
+            value = password.ProtectedPassword(value)
+        cred[key] = value
+    return cred
diff --git a/vdsm/supervdsmServer b/vdsm/supervdsmServer
index 36c67b1..1d2dd41 100755
--- a/vdsm/supervdsmServer
+++ b/vdsm/supervdsmServer
@@ -69,6 +69,7 @@
 from storage.iscsi import readSessionInfo as _readSessionInfo
 from supervdsm import _SuperVdsmManager
 from storage import hba
+from storage import iscsicred
 from storage import multipath
 from storage.fileUtils import chown, resolveGid, resolveUid
 from storage.fileUtils import validateAccess as _validateAccess
@@ -167,6 +168,11 @@
         return _readSessionInfo(sessionID)
 
     @logDecorator
+    def readTargetCredfile(self, target):
+        data = iscsicred._read_credfile(target)
+        return password.ProtectedPassword(data)
+
+    @logDecorator
     def getPathsStatus(self):
         return _getPathsStatus()
 


-- 
To view, visit https://gerrit.ovirt.org/43179
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I8f6a838f6b8e132d6b0c1a8135f2c28ef1e7f847
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer at redhat.com>


More information about the vdsm-patches mailing list