Change in vdsm[master]: gluster: Setup and verify ssl connection between nodes.

tjeyasin at redhat.com tjeyasin at redhat.com
Wed Aug 21 09:33:59 UTC 2013


Hello Ayal Baron, Bala.FA, Saggi Mizrahi, Dan Kenigsberg,

I'd like you to do a code review.  Please visit

    http://gerrit.ovirt.org/18355

to review the following change.

Change subject: gluster: Setup and verify ssl connection between nodes.
......................................................................

gluster: Setup and verify ssl connection between nodes.

This will be used in geo-replication session creation.
Because, there should be password-less ssh access between
at least one node of master volume and one node of slave
volume before creating geo-replication session.

Below new verbs are added

*glusterValidateSshConnection
*glusterSetupSshConnection

Change-Id: Ia6f040b1343998de4f8e28419c63e380240368db
Signed-off-by: Bala.FA <barumuga at redhat.com>
Signed-off-by: Timothy Asir <tjeyasin at redhat.com>
---
M client/vdsClientGluster.py
M vdsm.spec.in
M vdsm/gluster/api.py
M vdsm/gluster/exception.py
4 files changed, 191 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/55/18355/1

diff --git a/client/vdsClientGluster.py b/client/vdsClientGluster.py
index 90af83e..0ae7ec7 100644
--- a/client/vdsClientGluster.py
+++ b/client/vdsClientGluster.py
@@ -424,6 +424,30 @@
         pp.pprint(status)
         return status['status']['code'], status['status']['message']
 
+    def do_glusterValidateSshConnection(self, args):
+        params = self._eqSplit(args)
+        host = params.get('host', '')
+        fingerprint = params.get('fingerprint', '')
+        username = params.get('username', '')
+
+        status = self.s.glusterValidateSshConnection(host,
+                                                     fingerprint,
+                                                     username)
+        return status['status']['code'], status['status']['message']
+
+    def do_glusterSetupSshConnection(self, args):
+        params = self._eqSplit(args)
+        host = params.get('host', '')
+        fingerprint = params.get('fingerprint', '')
+        username = params.get('username', '')
+        password = params.get('password', '')
+
+        status = self.s.glusterSetupSshConnection(host,
+                                                  fingerprint,
+                                                  username,
+                                                  password)
+        return status['status']['code'], status['status']['message']
+
 
 def getGlusterCmdDict(serv):
     return \
@@ -705,4 +729,15 @@
               'not set'
               '(swift, glusterd, smb, memcached)'
               )),
+         'glusterValidateSshConnection': (
+             serv.do_glusterValidateSshConnection,
+             ('host=<host> fingerprint=<fingerprint> username=<username>',
+              'validate passwordless ssh connection'
+              )),
+         'glusterSetupSshConnection': (
+             serv.do_glusterSetupSshConnection,
+             ('host=<host> fingerprint=<fingerprint> username=<username> '
+              'password=<password>',
+              'setup passwordless ssh connection'
+              )),
          }
diff --git a/vdsm.spec.in b/vdsm.spec.in
index e2307e0..81d8f9f 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -524,6 +524,7 @@
 Requires: glusterfs-fuse
 Requires: glusterfs-rdma
 Requires: python-magic
+Requires: python-paramiko
 
 %description gluster
 Gluster plugin enables VDSM to serve Gluster functionalities.
diff --git a/vdsm/gluster/api.py b/vdsm/gluster/api.py
index 4bd8308..1d93150 100644
--- a/vdsm/gluster/api.py
+++ b/vdsm/gluster/api.py
@@ -19,11 +19,28 @@
 #
 
 from functools import wraps
+import socket
+import paramiko
+import logging
+import os
+import re
 
 from vdsm.define import doneCode
 import supervdsm as svdsm
+from vdsm.config import config
+from vdsm import utils
+import exception as ge
 
 _SUCCESS = {'status': doneCode}
+_KEYFILE = config.get('vars', 'trust_store_path') + '/keys/vdsmkey.pem'
+_sshKeyGenCommandPath = utils.CommandPath("ssh-keygen",
+                                          "/usr/bin/ssh-keygen",
+                                          )
+_SSH_COPY_ID_CMD = 'umask 077 && mkdir -p ~/.ssh && ' \
+    'cat >> ~/.ssh/authorized_keys && if test -x /sbin/restorecon; ' \
+    'then /sbin/restorecon ~/.ssh ~/.ssh/authorized_keys >/dev/null 2>&1; ' \
+    'else true; fi'
+paramiko.util.get_logger('paramiko').setLevel(logging.ERROR)
 
 GLUSTER_RPM_PACKAGES = (
     ('glusterfs', 'glusterfs'),
@@ -59,6 +76,57 @@
 
     wrapper.exportAsVerb = True
     return wrapper
+
+
+class VolumeStatus():
+    ONLINE = 'ONLINE'
+    OFFLINE = 'OFFLINE'
+
+
+class HostKeyMatchException(paramiko.SSHException):
+    def __init__(self, hostname, fingerprint, expected_fingerprint):
+        self.err = 'Fingerprint %s of host %s does not match with %s' % \
+            (fingerprint, hostname, expected_fingerprint)
+        paramiko.SSHException.__init__(self, self.err)
+        self.hostname = hostname
+        self.fingerprint = fingerprint
+        self.expected_fingerprint = expected_fingerprint
+
+
+class HostKeyMatchPolicy(paramiko.AutoAddPolicy):
+    def __init__(self, expected_fingerprint):
+        self.expected_fingerprint = expected_fingerprint
+
+    def missing_host_key(self, client, hostname, key):
+        s = paramiko.util.hexlify(key.get_fingerprint())
+        fingerprint = ':'.join(re.findall('..', s))
+        if fingerprint.upper() == self.expected_fingerprint.upper():
+            paramiko.AutoAddPolicy.missing_host_key(self, client, hostname,
+                                                    key)
+        else:
+            raise HostKeyMatchException(hostname, fingerprint,
+                                        self.expected_fingerprint)
+
+
+class GlusterSsh(paramiko.SSHClient):
+    def __init__(self, hostname, fingerprint, port=22, username=None,
+                 password=None, pkey=None, key_filenames=[], timeout=None,
+                 allow_agent=True, look_for_keys=True, compress=False):
+        paramiko.SSHClient.__init__(self)
+        key_file_list = []
+        if os.path.exists(_KEYFILE):
+            key_file_list.append(_KEYFILE)
+        key_file_list.append(key_filenames)
+        self.set_missing_host_key_policy(HostKeyMatchPolicy(fingerprint))
+        try:
+            paramiko.SSHClient.connect(self, hostname, port, username,
+                                       password, pkey, key_file_list, timeout,
+                                       allow_agent, look_for_keys, compress)
+        except socket.error, e:
+            err = ['%s: %s' % (hostname, e)]
+            raise ge.GlusterSshConnectionFailedException(err=err)
+        except HostKeyMatchException, e:
+            raise ge.GlusterSshHostKeyMismatchException(err=[e.err])
 
 
 class GlusterApi(object):
@@ -287,6 +355,57 @@
         status = self.svdsmProxy.glusterServicesGet(serviceNames)
         return {'services': status}
 
+    def _validateSshConnection(self, hostname, fingerprint, username):
+        try:
+            ssh = GlusterSsh(hostname,
+                             fingerprint,
+                             username=username)
+            ssh.close()
+            return True
+        except paramiko.SSHException, e:
+            raise ge.GlusterSshHostKeyAuthException(err=[str(e)])
+
+    @exportAsVerb
+    def validateSshConnection(self, hostname, fingerprint, username,
+                              options=None):
+        self._validateSshConnection(hostname, fingerprint, username)
+
+    @exportAsVerb
+    def setupSshConnection(self, hostname, fingerprint, username, password,
+                           options=None):
+        rc, out, err = utils.execCmd([_sshKeyGenCommandPath.cmd, '-y', '-f',
+                                      _KEYFILE])
+        if rc != 0:
+            raise ge.GlusterSshPubKeyGenerationFailedException(rc=rc, err=err)
+
+        try:
+            ssh = GlusterSsh(hostname,
+                             fingerprint,
+                             username=username,
+                             password=password)
+            c = ssh.get_transport().open_session()
+            c.exec_command(_SSH_COPY_ID_CMD)
+            stdin = c.makefile('wb')
+            stdout = c.makefile('rb')
+            stderr = c.makefile_stderr('rb')
+            stdin.write('\n'.join(out) + '\n')
+            stdin.flush()
+            stdin.close()
+            c.shutdown_write()
+            rc = c.recv_exit_status()
+            out = stdout.read().splitlines()
+            err = stderr.read().splitlines()
+            c.close()
+            ssh.close()
+            if rc != 0:
+                raise ge.GlusterSshSetupExecFailedException(rc=rc,
+                                                            out=out,
+                                                            err=err)
+        except paramiko.AuthenticationException, e:
+            raise ge.GlusterSshHostAuthException(err=[str(e)])
+
+        self._validateSshConnection(hostname, fingerprint, username)
+
 
 def getGlusterMethods(gluster):
     l = []
diff --git a/vdsm/gluster/exception.py b/vdsm/gluster/exception.py
index c569a9e..c9a0548 100644
--- a/vdsm/gluster/exception.py
+++ b/vdsm/gluster/exception.py
@@ -484,3 +484,39 @@
         prefix = "%s: " % (action)
         self.message = prefix + "Service action is not supported"
         self.err = [self.message]
+
+
+# Ssh
+class GlusterSshException(GlusterException):
+    code = 4500
+    message = "Gluster ssh exception"
+
+
+class GlusterSshConnectionFailedException(GlusterSshException):
+    code = 4501
+    message = "SSH connection failed"
+
+
+class GlusterSshHostKeyMismatchException(GlusterSshException):
+    code = 4502
+    message = "Host key match failed"
+
+
+class GlusterSshHostKeyAuthException(GlusterSshException):
+    code = 4503
+    message = "SSH host key authentication failed"
+
+
+class GlusterSshHostAuthException(GlusterSshException):
+    code = 4504
+    message = "SSH host authentication failed"
+
+
+class GlusterSshPubKeyGenerationFailedException(GlusterSshException):
+    code = 4505
+    message = "SSH public key generation failed"
+
+
+class GlusterSshSetupExecFailedException(GlusterSshException):
+    code = 4506
+    message = "SSH key setup execution failed"


-- 
To view, visit http://gerrit.ovirt.org/18355
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia6f040b1343998de4f8e28419c63e380240368db
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Timothy Asir <tjeyasin at redhat.com>
Gerrit-Reviewer: Ayal Baron <abaron at redhat.com>
Gerrit-Reviewer: Bala.FA <barumuga at redhat.com>
Gerrit-Reviewer: Dan Kenigsberg <danken at redhat.com>
Gerrit-Reviewer: Saggi Mizrahi <smizrahi at redhat.com>


More information about the vdsm-patches mailing list