[Checkins] SVN: zc.buildoutsftp/branches/jim-dev/ Added support for:
jim
cvs-admin at zope.org
Fri Jun 29 15:15:50 UTC 2012
Log message for revision 127193:
Added support for:
- Global-configuration settings.
- Global known-hosts files.
- Host-specific ssh keys.
Added mock-based tests for unix-like systems. Unfortunately, these
tests will fail for Windows and windows support, while present, is
untested.
Changed:
U zc.buildoutsftp/branches/jim-dev/README.txt
U zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/__init__.py
U zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/main.test
U zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/tests.py
-=-
Modified: zc.buildoutsftp/branches/jim-dev/README.txt
===================================================================
--- zc.buildoutsftp/branches/jim-dev/README.txt 2012-06-29 14:47:56 UTC (rev 127192)
+++ zc.buildoutsftp/branches/jim-dev/README.txt 2012-06-29 15:15:45 UTC (rev 127193)
@@ -55,10 +55,24 @@
Status and Change History
=========================
-This package is experimental. It seems to work based on manual
-testing. I'm still trying to figure out how to write automated tests
-for this.
+This package has been used for years on Linux and Mac OS X. The
+author doesn't use it on Windows, but, presumably, other people do.
+1.0.0 (2012/06/29)
+------------------
+
+Added support for:
+
+- Global-configuration settings.
+
+- Global known-hosts files.
+
+- Host-specific ssh keys.
+
+Added mock-based tests for unix-like systems. Unfortunately, these
+tests will fail for Windows and windows support, while present, is
+untested.
+
0.6.1 (2010/03/17)
------------------
Modified: zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/__init__.py
===================================================================
--- zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/__init__.py 2012-06-29 14:47:56 UTC (rev 127192)
+++ zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/__init__.py 2012-06-29 15:15:45 UTC (rev 127193)
@@ -40,13 +40,18 @@
'(?:' '([^@:]+)(?::([^@]*))?@' ')?'
'([^:]*)(?::(\d+))?$').match
+def deunixpath(path):
+ return os.path.join(*path.split('/'))
+
_configs = None
def _get_config(host):
global _configs
if _configs is None:
_configs = []
- for path in ('/etc/ssh/ssh_config', '/etc/ssh/ssh_config',
- os.path.expanduser('~/.ssh/config')):
+ for path in (
+ deunixpath('/etc/ssh/ssh_config'), deunixpath('/etc/ssh_config'),
+ os.path.expanduser(deunixpath('~/.ssh/config')),
+ ):
if os.path.exists(path):
config = paramiko.SSHConfig()
with open(path) as f:
@@ -91,15 +96,15 @@
host_keys = paramiko.HostKeys(user_host_keys)
else:
host_keys = {}
- global_host_keys = config.get('GlobalKnownHostsFile')
+ global_host_keys = config.get('globalknownhostsfile')
if not global_host_keys:
- for path in ('/etc/ssh/GlobalKnownHostsFile',
+ for path in ('/etc/ssh/ssh_known_hosts',
'/etc/ssh_known_hosts'):
if os.path.exists(path):
global_host_keys = path
break
if global_host_keys:
- host_keys.update(paramiko.HostKeys(user_host_keys))
+ host_keys.update(paramiko.HostKeys(global_host_keys))
return host_keys
class Result:
@@ -139,6 +144,9 @@
if trans is not False:
trans.close()
+ global _configs
+ _configs = None
+
atexit.register(cleanup)
class SFTPHandler(urllib2.BaseHandler):
@@ -188,9 +196,9 @@
raise
else:
keys = list(paramiko.Agent().get_keys())
- IdentityFile = config.get('IdentityFile')
+ IdentityFile = config.get('identityfile')
if IdentityFile:
- key = _open_key(IdentityFile)
+ key = _open_key(os.path.expanduser(IdentityFile))
if key is None:
logger.error('IdentityFile, %s, does not exist',
IdentityFile)
@@ -199,6 +207,7 @@
else:
for path in (
'~/.ssh/identity', '~/.ssh/id_rsa', '~/.ssh/id_dsa'):
+ path = deunixpath(path)
key = _open_key(os.path.expanduser(path))
if key is not None:
keys.insert(0, key)
Modified: zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/main.test
===================================================================
--- zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/main.test 2012-06-29 14:47:56 UTC (rev 127192)
+++ zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/main.test 2012-06-29 15:15:45 UTC (rev 127193)
@@ -16,7 +16,7 @@
A buildout object is passed to install, but install ignores it.
>>> handler = zc.buildoutsftp.SFTPHandler()
- >>> request = urllib2.Request('sftp://example.com')
+ >>> request = urllib2.Request('sftp://example.com/data')
Let's start with essentially no ssh support in the user's environment:
@@ -33,7 +33,8 @@
>>> host_keys = paramiko.HostKeys()
>>> host_keys.add('example.com', 'ssh-rsa', host_key)
>>> os.mkdir('.ssh', 0700)
- >>> host_keys.save(os.path.join('.ssh', 'known_hosts'))
+ >>> from os.path import join
+ >>> host_keys.save(join('.ssh', 'known_hosts'))
>>> handler.sftp_open(request)
Traceback (most recent call last):
@@ -45,5 +46,184 @@
Let's give them one:
>>> ukey = creds[(('example.com', 22), 'testuser')]['user_key']
- >>> ukey.write_private_key_file(os.path.join('.ssh', 'id_rsa'))
+ >>> ukey.write_private_key_file(join('.ssh', 'id_rsa'))
+ >>> handler.sftp_open(request)
+ ... # doctest: +NORMALIZE_WHITESPACE
+ Traceback (most recent call last):
+ ...
+ AuthenticationException:
+ ('Remote server authentication failed.', 'example.com')
+We got a bit farther, but the host key was wrong. Let's fix it:
+
+ >>> host_key = creds[(('example.com', 22), 'testuser')]['host_key']
+ >>> host_keys = paramiko.HostKeys()
+ >>> host_keys.add('example.com', 'ssh-rsa', host_key)
+ >>> host_keys.save(join('.ssh', 'known_hosts'))
+ >>> print handler.sftp_open(request).read()
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+That worked and we got a directory listing. If we access a file:
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data/index.html')
+ >>> print handler.sftp_open(request).read(),
+ Hi world!
+
+We get it's text.
+
+Now, let's try some variations:
+
+User key in ~/.ssh/id_dsa
+-------------------------
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> os.remove(join('.ssh', 'id_rsa'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data')
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: Authentication failed.
+
+ >>> ukey = paramiko.DSSKey.generate(1024)
+ >>> ukey.write_private_key_file(join('.ssh', 'id_dsa'))
+ >>> creds[(('example.com', 22), 'testuser')]['user_key'] = ukey
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+User key in ~/.ssh/identity
+---------------------------
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> os.remove(join('.ssh', 'id_dsa'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data')
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: Authentication failed.
+
+ >>> ukey.write_private_key_file(join('.ssh', 'identity'))
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+Host key in global known hosts file
+-----------------------------------
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> os.remove(join('.ssh', 'known_hosts'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data')
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: ('No stored host key', 'example.com')
+
+ >>> os.mkdir('etc')
+ >>> host_keys.save(join('etc', 'ssh_known_hosts'))
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> os.remove(join('etc', 'ssh_known_hosts'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data')
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: ('No stored host key', 'example.com')
+
+ >>> os.mkdir(join('etc', 'ssh'))
+ >>> host_keys.save(join('etc', 'ssh', 'ssh_known_hosts'))
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+Host key in global known hosts file with non-standard name
+----------------------------------------------------------
+
+ >>> os.remove(join('etc', 'ssh', 'ssh_known_hosts'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data')
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: ('No stored host key', 'example.com')
+
+ >>> with open(join('etc', 'ssh_config'), 'w') as f:
+ ... f.write('GlobalKnownHostsFile %s\n'
+ ... % join('etc', 'ssh', 'known_hosts'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> host_keys.save(join('etc', 'ssh', 'known_hosts'))
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+Alternate global config location
+--------------------------------
+
+ >>> os.remove(join('etc', 'ssh', 'known_hosts'))
+ >>> os.remove(join('etc', 'ssh_config'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> request = urllib2.Request('sftp://example.com/data')
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: ('No stored host key', 'example.com')
+
+ >>> with open(join('etc', 'ssh', 'ssh_config'), 'w') as f:
+ ... f.write('GlobalKnownHostsFile %s\n'
+ ... % join('etc', 'KnownHosts'))
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> host_keys.save(join('etc', 'KnownHosts'))
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+Host-specific user
+==================
+
+ >>> import getpass
+ >>> getpass.getuser.return_value = 'bob'
+
+ >>> zc.buildoutsftp.cleanup()
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: Authentication failed.
+
+ >>> with open(join('.ssh', 'config'), 'w') as f:
+ ... f.write('Host example.com\n'
+ ... 'User testuser\n'
+ ... )
+ >>> zc.buildoutsftp.cleanup()
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
+
+Host-specific key file
+======================
+
+ >>> os.remove(join('.ssh', 'identity'))
+ >>> zc.buildoutsftp.cleanup()
+ >>> print handler.sftp_open(request).read(),
+ Traceback (most recent call last):
+ ...
+ AuthenticationException: Authentication failed.
+
+ >>> with open(join('.ssh', 'config'), 'w') as f:
+ ... f.write('Host example.com\n'
+ ... 'User testuser\n'
+ ... 'IdentityFile ~/.ssh/id\n'
+ ... )
+ >>> zc.buildoutsftp.cleanup()
+ >>> ukey.write_private_key_file(join('.ssh', 'id'))
+ >>> print handler.sftp_open(request).read(),
+ <a href="sftp://example.com/data/moredata">moredata</a><br />
+ <a href="sftp://example.com/data/index.html">index.html</a><br />
Modified: zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/tests.py
===================================================================
--- zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/tests.py 2012-06-29 14:47:56 UTC (rev 127192)
+++ zc.buildoutsftp/branches/jim-dev/src/zc/buildoutsftp/tests.py 2012-06-29 15:15:45 UTC (rev 127193)
@@ -13,6 +13,7 @@
##############################################################################
from zope.testing import setupstack
import doctest
+import mimetypes
import mock
import os
import paramiko
@@ -30,6 +31,9 @@
def setup(test):
globs = test.globs
+
+ mimetypes.init()
+
setupstack.setUpDirectory(test)
setupstack.context_manager(test, mock.patch('urllib2.install_opener'))
setupstack.context_manager(test, mock.patch('urllib2.build_opener'))
@@ -72,15 +76,20 @@
self.addr = addr
def connect(self, username, pkey):
- if pkey != creds.get((self.addr, username))['user_key']:
- raise paramiko.AuthenticationException()
- self.username = username
+ try:
+ key = creds[(self.addr, username)]['user_key']
+ if pkey == key:
+ self.username = username
+ return
+ except KeyError:
+ pass
+ raise paramiko.AuthenticationException()
def close(self):
pass
def get_remote_server_key(self):
- return creds.get((self.addr, self.username))['user_key']
+ return creds.get((self.addr, self.username))['host_key']
@side_effect(
setupstack.context_manager(
@@ -103,10 +112,10 @@
else:
return base
- def stat(path):
+ def stat(self, path):
return os.stat(self._path(path))
- def listdir(self):
+ def listdir(self, path):
return os.listdir(self._path(path))
def open(self, path):
@@ -116,7 +125,7 @@
setupstack.context_manager(
test, mock.patch('paramiko.SFTPClient.from_transport')))
def from_transport(trans):
- return SFTPClient(addr)
+ return SFTPClient(trans)
os.makedirs(os.path.join('example.com:22', 'data', 'moredata'))
with open(os.path.join('example.com:22', 'data', 'index.html'), 'w') as f:
More information about the checkins
mailing list