From ac78347f2bc4a489c7e254c6c1d950fb45f240ad Mon Sep 17 00:00:00 2001 From: jctanner Date: Wed, 1 Feb 2017 10:39:40 -0500 Subject: [PATCH] Use a -short- custom hash for controlpersist path by default (#20843) * A method to validate and alter the ssh control path automatically. * First tries %C to use the shortened hash * On further failure, it removes section by section from the original path * Fix hostname * Implement bcoca's suggested changes * Remove unused option * Remove unused class var * Use to_string to avoid unicode error * Switch from to_text to to_bytes * Update the example config for the new controlpath feature --- examples/ansible.cfg | 12 +++++------- lib/ansible/constants.py | 2 +- lib/ansible/plugins/connection/ssh.py | 25 +++++++++++++++++++++++-- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/examples/ansible.cfg b/examples/ansible.cfg index 1cbc9afe2b..ed03f60ed3 100644 --- a/examples/ansible.cfg +++ b/examples/ansible.cfg @@ -310,16 +310,14 @@ # control_path_dir = /tmp/.ansible/cp #control_path_dir = ~/.ansible/cp -# The path to use for the ControlPath sockets. This defaults to -# "%(directory)s/ansible-ssh-%%h-%%p-%%r", however on some systems with -# very long hostnames or very long path names (caused by long user names or -# deeply nested home directories) this can exceed the character limit on -# file socket names (108 characters for most platforms). In that case, you -# may wish to shorten the string below. +# The path to use for the ControlPath sockets. This defaults to a hashed string of the hostname, +# port and username (empty string in the config). The hash mitigates a common problem users +# found with long hostames and the conventional %(directory)s/ansible-ssh-%%h-%%p-%%r format. +# In those cases, a "too long for Unix domain socket" ssh error would occur. # # Example: # control_path = %(directory)s/%%h-%%r -#control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r +#control_path = # Enabling pipelining reduces the number of SSH operations required to # execute a module on the remote server. This can result in a significant diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index dce01c0be3..7556be4476 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -331,7 +331,7 @@ ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'AN # to .format() in the future. be sure to read this: # http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/ and understand # that it may be a security risk to do so. -ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', u"%(directory)s/ansible-ssh-%%h-%%p-%%r") +ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', None) ANSIBLE_SSH_CONTROL_PATH_DIR = get_config(p, 'ssh_connection', 'control_path_dir', 'ANSIBLE_SSH_CONTROL_PATH_DIR', u'~/.ansible/cp') ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, value_type='boolean') ANSIBLE_SSH_RETRIES = get_config(p, 'ssh_connection', 'retries', 'ANSIBLE_SSH_RETRIES', 0, value_type='integer') diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py index 7d5443e5fe..135fe7a933 100644 --- a/lib/ansible/plugins/connection/ssh.py +++ b/lib/ansible/plugins/connection/ssh.py @@ -21,6 +21,7 @@ __metaclass__ = type import errno import fcntl +import hashlib import os import pty import select @@ -59,6 +60,10 @@ class Connection(ConnectionBase): super(Connection, self).__init__(*args, **kwargs) self.host = self._play_context.remote_addr + self.port = self._play_context.port + self.user = self._play_context.remote_user + self.control_path = C.ANSIBLE_SSH_CONTROL_PATH + self.control_path_dir = C.ANSIBLE_SSH_CONTROL_PATH_DIR # The connection is created by running ssh/scp/sftp from the exec_command, # put_file, and fetch_file methods, so we don't need to do any connection @@ -67,6 +72,16 @@ class Connection(ConnectionBase): def _connect(self): return self + @staticmethod + def _create_control_path(host, port, user): + '''Make a hash for the controlpath based on con attributes''' + pstring = '%s-%s-%s' % (host, port, user) + m = hashlib.sha1() + m.update(to_bytes(pstring)) + digest = m.hexdigest() + cpath = '%(directory)s/' + digest[:10] + return cpath + @staticmethod def _sshpass_available(): global SSHPASS_AVAILABLE @@ -228,7 +243,7 @@ class Connection(ConnectionBase): self._persistent = True if not controlpath: - cpdir = unfrackpath(C.ANSIBLE_SSH_CONTROL_PATH_DIR) + cpdir = unfrackpath(self.control_path_dir) b_cpdir = to_bytes(cpdir, errors='surrogate_or_strict') # The directory must exist and be writable. @@ -236,7 +251,13 @@ class Connection(ConnectionBase): if not os.access(b_cpdir, os.W_OK): raise AnsibleError("Cannot write to ControlPath %s" % to_native(cpdir)) - b_args = (b"-o", b"ControlPath=" + to_bytes(C.ANSIBLE_SSH_CONTROL_PATH % dict(directory=cpdir), errors='surrogate_or_strict')) + if not self.control_path: + self.control_path = self._create_control_path( + self.host, + self.port, + self.user + ) + b_args = (b"-o", b"ControlPath=" + to_bytes(self.control_path % dict(directory=cpdir), errors='surrogate_or_strict')) self._add_args(b_command, b_args, u"found only ControlPersist; added ControlPath") # Finally, we add any caller-supplied extras.