Merge pull request #30894 from terminalmage/issue30858

git module/state: Handle identity files more gracefully
This commit is contained in:
Mike Place 2016-02-04 16:55:01 -07:00
commit 3d3321ab92
5 changed files with 284 additions and 108 deletions

View File

@ -6,7 +6,6 @@ from __future__ import absolute_import
# Import python libs # Import python libs
import copy import copy
import errno
import logging import logging
import os import os
import re import re
@ -155,7 +154,9 @@ def _git_run(command, cwd=None, runas=None, identity=None,
env = {} env = {}
if identity: if identity:
stderrs = [] _salt_cli = __opts__.get('__cli', '')
errors = []
missing_keys = []
# if the statefile provides multiple identities, they need to be tried # if the statefile provides multiple identities, they need to be tried
# (but also allow a string instead of a list) # (but also allow a string instead of a list)
@ -165,6 +166,11 @@ def _git_run(command, cwd=None, runas=None, identity=None,
# try each of the identities, independently # try each of the identities, independently
for id_file in identity: for id_file in identity:
if not os.path.isfile(id_file):
missing_keys.append(id_file)
log.warning('Identity file {0} does not exist'.format(id_file))
continue
env = { env = {
'GIT_IDENTITY': id_file 'GIT_IDENTITY': id_file
} }
@ -198,6 +204,21 @@ def _git_run(command, cwd=None, runas=None, identity=None,
os.chown(tmp_file, __salt__['file.user_to_uid'](runas), -1) os.chown(tmp_file, __salt__['file.user_to_uid'](runas), -1)
env['GIT_SSH'] = tmp_file env['GIT_SSH'] = tmp_file
if 'salt-call' not in _salt_cli \
and __salt__['ssh.key_is_encrypted'](id_file):
errors.append(
'Identity file {0} is passphrase-protected and cannot be '
'used in a non-interactive command. Using salt-call from '
'the minion will allow a passphrase-protected key to be '
'used.'.format(id_file)
)
continue
log.info(
'Attempting git authentication using identity file {0}'
.format(id_file)
)
try: try:
result = __salt__['cmd.run_all']( result = __salt__['cmd.run_all'](
command, command,
@ -213,17 +234,29 @@ def _git_run(command, cwd=None, runas=None, identity=None,
if not salt.utils.is_windows() and 'GIT_SSH' in env: if not salt.utils.is_windows() and 'GIT_SSH' in env:
os.remove(env['GIT_SSH']) os.remove(env['GIT_SSH'])
# if the command was successful, no need to try additional IDs # If the command was successful, no need to try additional IDs
if result['retcode'] == 0: if result['retcode'] == 0:
return result return result
else: else:
stderr = \ stderr = \
salt.utils.url.redact_http_basic_auth(result['stderr']) salt.utils.url.redact_http_basic_auth(result['stderr'])
stderrs.append(stderr) errors.append(stderr)
# we've tried all IDs and still haven't passed, so error out # We've tried all IDs and still haven't passed, so error out
if failhard: if failhard:
raise CommandExecutionError('\n\n'.join(stderrs)) msg = (
'Unable to authenticate using identity file:\n\n{0}'.format(
'\n'.join(errors)
)
)
if missing_keys:
if errors:
msg += '\n\n'
msg += (
'The following identity file(s) were not found: {0}'
.format(', '.join(missing_keys))
)
raise CommandExecutionError(msg)
return result return result
else: else:
@ -677,13 +710,19 @@ def clone(cwd,
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
https_user https_user
Set HTTP Basic Auth username. Only accepted for HTTPS URLs. Set HTTP Basic Auth username. Only accepted for HTTPS URLs.
@ -1369,13 +1408,19 @@ def fetch(cwd,
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
ignore_retcode : False ignore_retcode : False
If ``True``, do not log an error to the minion log if the git command If ``True``, do not log an error to the minion log if the git command
returns a nonzero exit status. returns a nonzero exit status.
@ -1886,20 +1931,8 @@ def list_worktrees(cwd, stale=False, user=None, **kwargs):
break break
return ret return ret
except (IOError, OSError) as exc: except (IOError, OSError) as exc:
if exc.errno == errno.ENOENT: # Raise a CommandExecutionError
raise CommandExecutionError( salt.utils.files.process_read_exception(exc, path)
'{0} does not exist'.format(path)
)
elif exc.errno == errno.EACCES:
raise CommandExecutionError(
'Permission denied reading from {0}'.format(path)
)
else:
raise CommandExecutionError(
'Error {0} encountered reading from {1}: {2}'.format(
exc.errno, path, exc.strerror
)
)
for worktree_name in os.listdir(worktree_root): for worktree_name in os.listdir(worktree_root):
admin_dir = os.path.join(worktree_root, worktree_name) admin_dir = os.path.join(worktree_root, worktree_name)
@ -2009,13 +2042,19 @@ def ls_remote(cwd=None,
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
https_user https_user
Set HTTP Basic Auth username. Only accepted for HTTPS URLs. Set HTTP Basic Auth username. Only accepted for HTTPS URLs.
@ -2427,13 +2466,19 @@ def pull(cwd, opts='', user=None, identity=None, ignore_retcode=False):
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
ignore_retcode : False ignore_retcode : False
If ``True``, do not log an error to the minion log if the git command If ``True``, do not log an error to the minion log if the git command
returns a nonzero exit status. returns a nonzero exit status.
@ -2507,13 +2552,19 @@ def push(cwd,
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
ignore_retcode : False ignore_retcode : False
If ``True``, do not log an error to the minion log if the git command If ``True``, do not log an error to the minion log if the git command
returns a nonzero exit status. returns a nonzero exit status.
@ -2703,13 +2754,19 @@ def remote_refs(url,
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
https_user https_user
Set HTTP Basic Auth username. Only accepted for HTTPS URLs. Set HTTP Basic Auth username. Only accepted for HTTPS URLs.
@ -3275,13 +3332,19 @@ def submodule(cwd,
.. warning:: .. warning::
Key must be passphraseless to allow for non-interactive login. For Unless Salt is invoked from the minion using ``salt-call``, the
greater security with passphraseless private keys, see the key(s) must be passphraseless. For greater security with
`sshd(8)`_ manpage for information on securing the keypair from the passphraseless private keys, see the `sshd(8)`_ manpage for
remote side in the ``authorized_keys`` file. information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT .. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
ignore_retcode : False ignore_retcode : False
If ``True``, do not log an error to the minion log if the git command If ``True``, do not log an error to the minion log if the git command
returns a nonzero exit status. returns a nonzero exit status.

View File

@ -2,23 +2,26 @@
''' '''
Manage client ssh components Manage client ssh components
.. note:: This module requires the use of MD5 hashing. Certain .. note::
security audits may not permit the use of MD5. For those cases,
this module should be disabled or removed. This module requires the use of MD5 hashing. Certain security audits may
not permit the use of MD5. For those cases, this module should be disabled
or removed.
''' '''
from __future__ import absolute_import from __future__ import absolute_import
# Import python libs # Import python libs
import binascii
import hashlib
import logging
import os import os
import re import re
import hashlib
import binascii
import logging
import subprocess import subprocess
# Import salt libs # Import salt libs
import salt.utils import salt.utils
import salt.utils.files
import salt.utils.decorators as decorators import salt.utils.decorators as decorators
from salt.exceptions import ( from salt.exceptions import (
SaltInvocationError, SaltInvocationError,
@ -286,7 +289,10 @@ def host_keys(keydir=None, private=True):
for fn_ in os.listdir(keydir): for fn_ in os.listdir(keydir):
if fn_.startswith('ssh_host_'): if fn_.startswith('ssh_host_'):
if fn_.endswith('.pub') is False and private is False: if fn_.endswith('.pub') is False and private is False:
log.info('Skipping private key file {0} as private is set to False'.format(fn_)) log.info(
'Skipping private key file {0} as private is set to False'
.format(fn_)
)
continue continue
top = fn_.split('.') top = fn_.split('.')
@ -296,11 +302,14 @@ def host_keys(keydir=None, private=True):
kname += '.{0}'.format(top[1]) kname += '.{0}'.format(top[1])
try: try:
with salt.utils.fopen(os.path.join(keydir, fn_), 'r') as _fh: with salt.utils.fopen(os.path.join(keydir, fn_), 'r') as _fh:
# As of RFC 4716 "a key file is a text file, containing a sequence of lines", # As of RFC 4716 "a key file is a text file, containing a
# although some SSH implementations (e.g. OpenSSH) manage their own format(s). # sequence of lines", although some SSH implementations
# Please see #20708 for a discussion about how to handle SSH key files in the future # (e.g. OpenSSH) manage their own format(s). Please see
# #20708 for a discussion about how to handle SSH key files
# in the future
keys[kname] = _fh.readline() keys[kname] = _fh.readline()
# only read the whole file if it is not in the legacy 1.1 binary format # only read the whole file if it is not in the legacy 1.1
# binary format
if keys[kname] != "SSH PRIVATE KEY FILE FORMAT 1.1\n": if keys[kname] != "SSH PRIVATE KEY FILE FORMAT 1.1\n":
keys[kname] += _fh.read() keys[kname] += _fh.read()
keys[kname] = keys[kname].strip() keys[kname] = keys[kname].strip()
@ -445,7 +454,8 @@ def rm_auth_key_from_file(user,
saltenv='base', saltenv='base',
env=None): env=None):
''' '''
Remove an authorized key from the specified user's authorized key file, using a file as source Remove an authorized key from the specified user's authorized key file,
using a file as source
CLI Example: CLI Example:
@ -1085,9 +1095,9 @@ def user_keys(user=None, pubfile=None, prvfile=None):
salt '*' ssh.user_keys user=user1 prvfile=False salt '*' ssh.user_keys user=user1 prvfile=False
salt '*' ssh.user_keys user="['user1','user2'] pubfile=id_rsa.pub prvfile=id_rsa salt '*' ssh.user_keys user="['user1','user2'] pubfile=id_rsa.pub prvfile=id_rsa
As you can see you can tell Salt not to read from the user's private (or public) key file by setting the As you can see you can tell Salt not to read from the user's private (or
file path to ``False``. This can be useful to prevent Salt from publishing private data via Salt Mine or public) key file by setting the file path to ``False``. This can be useful
others. to prevent Salt from publishing private data via Salt Mine or others.
''' '''
if not user: if not user:
user = __salt__['user.list_users']() user = __salt__['user.list_users']()
@ -1111,7 +1121,8 @@ def user_keys(user=None, pubfile=None, prvfile=None):
userKeys.append(pubfile) userKeys.append(pubfile)
elif pubfile is not False: elif pubfile is not False:
# Add the default public keys # Add the default public keys
userKeys += ['id_rsa.pub', 'id_dsa.pub', 'id_ecdsa.pub', 'id_ed25519.pub'] userKeys += ['id_rsa.pub', 'id_dsa.pub',
'id_ecdsa.pub', 'id_ed25519.pub']
if prvfile: if prvfile:
userKeys.append(prvfile) userKeys.append(prvfile)
@ -1188,3 +1199,36 @@ def _hostname_and_port_to_ssh_hostname(hostname, port=DEFAULT_SSH_PORT):
return hostname return hostname
else: else:
return '[{0}]:{1}'.format(hostname, port) return '[{0}]:{1}'.format(hostname, port)
def key_is_encrypted(key):
'''
.. versionadded:: 2015.8.7
Function to determine whether or not a private key is encrypted with a
passphrase.
Checks key for a ``Proc-Type`` header with ``ENCRYPTED`` in the value. If
found, returns ``True``, otherwise returns ``False``.
CLI Example:
.. code-block:: bash
salt '*' ssh.key_is_encrypted /root/id_rsa
'''
try:
with salt.utils.fopen(key, 'r') as fp_:
key_data = fp_.read()
except (IOError, OSError) as exc:
# Raise a CommandExecutionError
salt.utils.files.process_read_exception(exc, key)
is_private_key = re.search(r'BEGIN (?:\w+\s)*PRIVATE KEY', key_data)
is_encrypted = 'ENCRYPTED' in key_data
del key_data
if not is_private_key:
raise CommandExecutionError('{0} is not a private key'.format(key))
return is_encrypted

View File

@ -123,6 +123,20 @@ def _fail(ret, msg, comments=None):
return ret return ret
def _failed_fetch(ret, exc, comments=None):
msg = (
'Fetch failed. Set \'force_fetch\' to True to force the fetch if the '
'failure was due to it being non-fast-forward. Output of the fetch '
'command follows:\n\n{0}'.format(_strip_exc(exc))
)
return _fail(ret, msg, comments)
def _failed_submodule_update(ret, exc, comments=None):
msg = 'Failed to update submodules: ' + _strip_exc(exc)
return _fail(ret, msg, comments)
def _not_fast_forward(ret, pre, post, branch, local_branch, comments): def _not_fast_forward(ret, pre, post, branch, local_branch, comments):
return _fail( return _fail(
ret, ret,
@ -274,7 +288,42 @@ def latest(name,
with tags or revision IDs. with tags or revision IDs.
identity identity
A path on the minion server to a private key to use over SSH Path to a private key to use for ssh URLs. This can be either a single
string, or a list of strings. For example:
.. code-block:: yaml
# Single key
git@github.com:user/repo.git:
git.latest:
- user: deployer
- identity: /home/deployer/.ssh/id_rsa
# Two keys
git@github.com:user/repo.git:
git.latest:
- user: deployer
- identity:
- /home/deployer/.ssh/id_rsa
- /home/deployer/.ssh/id_rsa_alternate
If multiple keys are specified, they will be tried one-by-one in order
for each git command which needs to authenticate.
.. warning::
Unless Salt is invoked from the minion using ``salt-call``, the
key(s) must be passphraseless. For greater security with
passphraseless private keys, see the `sshd(8)`_ manpage for
information on securing the keypair from the remote side in the
``authorized_keys`` file.
.. _`sshd(8)`: http://www.man7.org/linux/man-pages/man8/sshd.8.html#AUTHORIZED_KEYS_FILE%20FORMAT
.. versionchanged:: 2015.8.7
Salt will no longer attempt to use passphrase-protected keys unless
invoked from the minion using ``salt-call``, to prevent blocking
waiting for user input.
https_user https_user
HTTP Basic Auth username for HTTPS (only) clones HTTP Basic Auth username for HTTPS (only) clones
@ -952,18 +1001,7 @@ def latest(name,
user=user, user=user,
identity=identity) identity=identity)
except CommandExecutionError as exc: except CommandExecutionError as exc:
msg = 'Fetch failed' return _failed_fetch(ret, exc, comments)
if isinstance(exc, CommandExecutionError):
msg += (
'. Set \'force_fetch\' to True to force '
'the fetch if the failure was due to it '
'bein non-fast-forward. Output of the '
'fetch command follows:\n\n'
)
msg += _strip_exc(exc)
else:
msg += ':\n\n' + str(exc)
return _fail(ret, msg, comments)
else: else:
if fetch_changes: if fetch_changes:
comments.append( comments.append(
@ -1117,11 +1155,15 @@ def latest(name,
# TODO: Figure out how to add submodule update info to # TODO: Figure out how to add submodule update info to
# test=True return data, and changes dict. # test=True return data, and changes dict.
if submodules: if submodules:
__salt__['git.submodule'](target, try:
'update', __salt__['git.submodule'](
opts=['--init', '--recursive'], target,
user=user, 'update',
identity=identity) opts=['--init', '--recursive'],
user=user,
identity=identity)
except CommandExecutionError as exc:
return _failed_submodule_update(ret, exc, comments)
elif bare: elif bare:
if __opts__['test']: if __opts__['test']:
msg = ( msg = (
@ -1141,18 +1183,7 @@ def latest(name,
user=user, user=user,
identity=identity) identity=identity)
except CommandExecutionError as exc: except CommandExecutionError as exc:
msg = 'Fetch failed' return _failed_fetch(ret, exc, comments)
if isinstance(exc, CommandExecutionError):
msg += (
'. Set \'force_fetch\' to True to force '
'the fetch if the failure was due to it '
'bein non-fast-forward. Output of the '
'fetch command follows:\n\n'
)
msg += _strip_exc(exc)
else:
msg += ':\n\n' + str(exc)
return _fail(ret, msg, comments)
else: else:
comments.append( comments.append(
'Bare repository at {0} was fetched{1}'.format( 'Bare repository at {0} was fetched{1}'.format(
@ -1260,13 +1291,18 @@ def latest(name,
# We're cloning a fresh repo, there is no local branch or revision # We're cloning a fresh repo, there is no local branch or revision
local_branch = local_rev = None local_branch = local_rev = None
__salt__['git.clone'](target, try:
name, __salt__['git.clone'](target,
user=user, name,
opts=clone_opts, user=user,
identity=identity, opts=clone_opts,
https_user=https_user, identity=identity,
https_pass=https_pass) https_user=https_user,
https_pass=https_pass)
except CommandExecutionError as exc:
msg = 'Clone failed: {0}'.format(_strip_exc(exc))
return _fail(ret, msg, comments)
ret['changes']['new'] = name + ' => ' + target ret['changes']['new'] = name + ' => ' + target
comments.append( comments.append(
'{0} cloned to {1}{2}'.format( '{0} cloned to {1}{2}'.format(
@ -1376,11 +1412,14 @@ def latest(name,
comments.append(upstream_action) comments.append(upstream_action)
if submodules and remote_rev: if submodules and remote_rev:
__salt__['git.submodule'](target, try:
'update', __salt__['git.submodule'](target,
opts=['--init', '--recursive'], 'update',
user=user, opts=['--init', '--recursive'],
identity=identity) user=user,
identity=identity)
except CommandExecutionError as exc:
return _failed_submodule_update(ret, exc, comments)
try: try:
new_rev = __salt__['git.revision']( new_rev = __salt__['git.revision'](

View File

@ -1,6 +1,18 @@
#!/bin/sh
OPTS="-oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no" OPTS="-oStrictHostKeyChecking=no -oPasswordAuthentication=no -oKbdInteractiveAuthentication=no -oChallengeResponseAuthentication=no"
if [ ! -z "$GIT_IDENTITY" ]; then if test -n "$GIT_IDENTITY"; then
OPTS="$OPTS -i $GIT_IDENTITY" OPTS="$OPTS -i $GIT_IDENTITY"
# Check /etc/ssh and /usr/local/etc/ssh for the global ssh_config
if test -e "/etc/ssh/ssh_config"; then
OVERRIDE_SSH_CONFIG="/etc/ssh/ssh_config"
elif test -e "/usr/local/etc/ssh/ssh_config"; then
OVERRIDE_SSH_CONFIG="/usr/local/etc/ssh/ssh_config"
else
OVERRIDE_SSH_CONFIG=""
fi
# If a global ssh_config was found, add it to the opts. This keeps the
# user's ~/.ssh/config from overriding the "-i" param.
if test -n "$OVERRIDE_SSH_CONFIG"; then
OPTS="$OPTS -F $OVERRIDE_SSH_CONFIG"
fi
fi fi
exec ssh $OPTS "$@" exec ssh $OPTS "$@"

View File

@ -112,3 +112,21 @@ def rename(src, dst):
) )
) )
os.rename(src, dst) os.rename(src, dst)
def process_read_exception(exc, path):
'''
Common code for raising exceptions when reading a file fails
'''
if exc.errno == errno.ENOENT:
raise CommandExecutionError('{0} does not exist'.format(path))
elif exc.errno == errno.EACCES:
raise CommandExecutionError(
'Permission denied reading from {0}'.format(path)
)
else:
raise CommandExecutionError(
'Error {0} encountered reading from {1}: {2}'.format(
exc.errno, path, exc.strerror
)
)