def start_server(authkey='PublicKey', address=None, port=0, prefix='server', allowed_hosts=None, allowed_users=None, allow_shell=False, allowed_types=None, timeout=None, tunnel=False, resources=None, log_prefix=None): """ Start an :class:`ObjServerFactory` service in a separate process in the current directory. authkey: string Authorization key, must be matched by clients. address: string IPv4 address, hostname, or pipe name. Default is the host's default IPv4 address. port: int Server port (default of 0 implies next available port). Note that ports below 1024 typically require special privileges. If port is negative, then a local pipe is used for communication. prefix: string Prefix for server config file and stdout/stderr file. allowed_hosts: list(string) Host address patterns to check against. Required if `port` >= 0. Ignored if `allowed_users` is specified. allowed_users: dict Dictionary of users and corresponding public keys allowed access. If None, *any* user may access. If empty, no user may access. The host portions of user strings are used for address patterns. allow_shell: bool If True, :meth:`execute_command` and :meth:`load_model` are allowed. Use with caution! allowed_types: list(string) Names of types which may be created. If None, then allow types listed by :meth:`get_available_types`. If empty, no types are allowed. timeout: int Seconds to wait for server to start. Note that public key generation can take a while. The default value of None will use an internally computed value based on host type (and for Windows, the availability of pyWin32). tunnel: bool If True, report host IP address but listen for connections from a local SSH tunnel. resources: string Filename for resource configuration. log_prefix: string Name used to identify remote remote logging messages from server. Implies that the local process will be receiving the messages. Returns ``(server_proc, config_filename)``. """ if timeout is None: if sys.platform == 'win32' and not HAVE_PYWIN32: #pragma no cover timeout = 120 else: timeout = 30 server_key = prefix+'.key' server_cfg = prefix+'.cfg' server_out = prefix+'.out' for path in (server_cfg, server_out): if os.path.exists(path): os.remove(path) with open(server_key, 'w') as out: out.write('%s\n' % authkey) factory_path = pkg_resources.resource_filename('openmdao.main', 'objserverfactory.py') args = ['python', factory_path, '--port', str(port), '--prefix', prefix] if address is not None: args.extend(['--address', address]) if tunnel: args.append('--tunnel') if resources is not None: args.append('--resources') args.append(resources) if allowed_users is not None: write_authorized_keys(allowed_users, 'users.allow', logging.getLogger()) args.extend(['--users', 'users.allow']) else: args.append('--allow-public') if port >= 0: if allowed_hosts is None: allowed_hosts = [socket.gethostbyname(socket.gethostname())] if allowed_hosts[0].startswith('127.') and \ '127.0.0.1' not in allowed_hosts: allowed_hosts.append('127.0.0.1') with open('hosts.allow', 'w') as out: for pattern in allowed_hosts: out.write('%s\n' % pattern) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('hosts.allow') else: #pragma no cover logging.warning("Can't make hosts.allow private") if allow_shell: args.append('--allow-shell') if allowed_types is not None: with open('types.allow', 'w') as out: for typname in allowed_types: out.write('%s\n' % typname) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('types.allow') else: #pragma no cover logging.warning("Can't make types.allow private") args.extend(['--types', 'types.allow']) if log_prefix is not None: log_host = socket.gethostname() log_port = logging_port(log_host, log_host) args.extend(['--log-host', log_host, '--log-port', str(log_port)]) if log_prefix: # Could be null (for default). args.extend(['--log-prefix', log_prefix]) proc = ShellProc(args, stdout=server_out, stderr=STDOUT) try: # Wait for valid server_cfg file. retry = 0 while (not os.path.exists(server_cfg)) or \ (os.path.getsize(server_cfg) == 0): return_code = proc.poll() if return_code: error_msg = proc.error_message(return_code) raise RuntimeError('Server startup failed %s' % error_msg) retry += 1 if retry < 10*timeout: time.sleep(.1) # Hard to cause a startup timeout. else: #pragma no cover proc.terminate(timeout) raise RuntimeError('Server startup timeout') return (proc, server_cfg) finally: if os.path.exists(server_key): os.remove(server_key)
def test_allowed_hosts(self): logging.debug('') logging.debug('test_allowed_hosts') hostname = socket.gethostname() host_ipv4 = socket.gethostbyname(hostname) dot = host_ipv4.rfind('.') domain_ipv4 = host_ipv4[:dot+1] good_data = """ # Local host IPv4. %s # Local domain IPv4. %s # Local host name. %s """ % (host_ipv4, domain_ipv4, hostname) gibberish = '$^&*' bad_data = good_data + """ # Gibberish. %s """ % gibberish # Try good data. with open('hosts.allow', 'w') as out: out.write(good_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('hosts.allow') try: allowed_hosts = read_allowed_hosts('hosts.allow') finally: os.remove('hosts.allow') self.assertEqual(len(allowed_hosts), 3) self.assertEqual(allowed_hosts[0], host_ipv4) self.assertEqual(allowed_hosts[1], domain_ipv4) self.assertEqual(allowed_hosts[2], host_ipv4) # Try bad data. with open('hosts.allow', 'w') as out: out.write(bad_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('hosts.allow') try: allowed_hosts = read_allowed_hosts('hosts.allow') except RuntimeError as exc: self.assertEqual(str(exc), "1 errors in 'hosts.allow', check log for details") else: expected = 3 try: # This actally resolves in some environments. socket.gethostbyname(gibberish) except socket.gaierror: pass else: expected += 1 self.assertEqual(len(allowed_hosts), expected) self.assertEqual(allowed_hosts[0], host_ipv4) self.assertEqual(allowed_hosts[1], domain_ipv4) self.assertEqual(allowed_hosts[2], host_ipv4) finally: os.remove('hosts.allow') # Check AF_INET addresses. logger = logging.getLogger() self.assertTrue(is_legal_connection((host_ipv4, 0), allowed_hosts, logger)) domain_host = domain_ipv4 + '123' self.assertTrue(is_legal_connection((domain_host, 0), allowed_hosts, logger)) self.assertFalse(is_legal_connection(('0.0.0.0', 0), allowed_hosts, logger)) # Check AF_UNIX address. self.assertTrue(is_legal_connection('/tmp/pipe', allowed_hosts, logger)) # Try nonexistant file. assert_raises(self, "read_allowed_hosts('no-such-file')", globals(), locals(), RuntimeError, "'no-such-file' does not exist") # Try insecure file. if sys.platform != 'win32' or HAVE_PYWIN32: with open('hosts.allow', 'w') as out: out.write('\n') os.chmod('hosts.allow', 0666) try: assert_raises(self, "read_allowed_hosts('hosts.allow')", globals(), locals(), RuntimeError, "'hosts.allow' is not private") finally: os.remove('hosts.allow')
def start_server(authkey='PublicKey', address=None, port=0, prefix='server', allowed_hosts=None, allowed_users=None, allow_shell=False, allowed_types=None, timeout=None, tunnel=False, resources=None, log_prefix=None): """ Start an :class:`ObjServerFactory` service in a separate process in the current directory. authkey: string Authorization key; must be matched by clients. address: string IPv4 address, hostname, or pipe name. Default is the host's default IPv4 address. port: int Server port (default of 0 implies next available port). Note that ports below 1024 typically require special privileges. If port is negative, then a local pipe is used for communication. prefix: string Prefix for server config file and stdout/stderr file. allowed_hosts: list(string) Host address patterns to check against. Required if `port` >= 0. Ignored if `allowed_users` is specified. allowed_users: dict Dictionary of users and corresponding public keys allowed access. If None, *any* user may access. If empty, no user may access. The host portions of user strings are used for address patterns. allow_shell: bool If True, :meth:`execute_command` and :meth:`load_model` are allowed. Use with caution! allowed_types: list(string) Names of types which may be created. If None, then allow types listed by :meth:`get_available_types`. If empty, no types are allowed. timeout: int Seconds to wait for server to start. Note that public key generation can take a while. The default value of None will use an internally computed value based on host type (and for Windows, the availability of pyWin32). tunnel: bool If True, report host IP address but listen for connections from a local SSH tunnel. resources: string Filename for resource configuration. log_prefix: string Name used to identify remote remote logging messages from server. Implies that the local process will be receiving the messages. Returns ``(server_proc, config_filename)``. """ if timeout is None: if sys.platform == 'win32' and not HAVE_PYWIN32: # pragma no cover timeout = 120 else: timeout = 30 server_key = prefix + '.key' server_cfg = prefix + '.cfg' server_out = prefix + '.out' for path in (server_cfg, server_out): if os.path.exists(path): os.remove(path) with open(server_key, 'w') as out: out.write('%s\n' % authkey) factory_path = pkg_resources.resource_filename('openmdao.main', 'objserverfactory.py') args = ['python', factory_path, '--port', str(port), '--prefix', prefix] if address is not None: args.extend(['--address', address]) if tunnel: args.append('--tunnel') if resources is not None: args.append('--resources') args.append(resources) if allowed_users is not None: write_authorized_keys(allowed_users, 'users.allow', logging.getLogger()) args.extend(['--users', 'users.allow']) else: args.append('--allow-public') if port >= 0: if allowed_hosts is None: allowed_hosts = [socket.gethostbyname(socket.gethostname())] if allowed_hosts[0].startswith('127.') and \ '127.0.0.1' not in allowed_hosts: allowed_hosts.append('127.0.0.1') with open('hosts.allow', 'w') as out: for pattern in allowed_hosts: out.write('%s\n' % pattern) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('hosts.allow') else: # pragma no cover logging.warning("Can't make hosts.allow private") if allow_shell: args.append('--allow-shell') if allowed_types is not None: with open('types.allow', 'w') as out: for typname in allowed_types: out.write('%s\n' % typname) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('types.allow') else: # pragma no cover logging.warning("Can't make types.allow private") args.extend(['--types', 'types.allow']) if log_prefix is not None: log_host = socket.gethostname() log_port = logging_port(log_host, log_host) args.extend(['--log-host', log_host, '--log-port', str(log_port)]) if log_prefix: # Could be null (for default). args.extend(['--log-prefix', log_prefix]) proc = ShellProc(args, stdout=server_out, stderr=STDOUT) try: # Wait for valid server_cfg file. retry = 0 while (not os.path.exists(server_cfg)) or \ (os.path.getsize(server_cfg) == 0): return_code = proc.poll() if return_code: error_msg = proc.error_message(return_code) raise RuntimeError('Server startup failed %s' % error_msg) retry += 1 if retry < 10 * timeout: time.sleep(.1) # Hard to cause a startup timeout. else: # pragma no cover proc.terminate(timeout) raise RuntimeError('Server startup timeout') return (proc, server_cfg) finally: if os.path.exists(server_key): os.remove(server_key)
def test_authorized_keys(self): logging.debug('') logging.debug('test_authorized_keys') # Try various line formats. hostname = socket.gethostname() good_key_data = """ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAt9gTm9qX3pKOvFbn8vdkWL/W4kAdtNxRQQXO6QXX7ihuYxv09ZMuqkFPCD1ZxwZNZG0BYstSytPyYQDAGbOglmsjfQ0PRtwDLvK4utGiGLuRsf8ig/cS8NDfSJ/I1B+DBlV1uMaGmzamsFDsavv4Qxf/X50Fl5JTBiPp9W17xkz+JyDCsNMaQd2iSx+GjLbxT/QG2xM9/qrF8bQAAMLdNoKHwVNW+lLXyww6YI9pPj7Tep/dg3xk5Ggf5L6eJGRzmJVMYSfFK+TIX4r49SNddo3Vy/K2H02Yxu6dIBXUTwa+AUC+Mfh5LisAJiM/Oj4NBngWVRgDjN9NH6nQD08R8Q== user1@%(host)s ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAv7QM8MwxkX9yCKIebEH0o14b6Uik3KZnkQo2uF0NyuzDeeZntFym7v0mx7HV4KncjA5Ix2UBw4VKB2virDInO/YKYOC3ZqEJH/CvJkBFggPaZyJyzrEname0+NRXg+PnB2yIDKH0dpwEKVDkwAhEaAqcb9xoahEgXmd4kOmNGylJcwAJhSNqAC9BJO+gAdukGmKodM3nkwKo1BJc2ozqoYar8MYH/FQK8GPBOp4w2LHlm2yXuPB/dqd9/b9N4/ivf5LEthNMn1AnLS37tZIbQ4rSaxLGb72p0iBHSM5oHh1JKDn3mGDKIGxR1cxQ6PuuH6wNB5giNU9U76M4y2QGvw== user2@%(host)s # another user entry ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user3@%(host)s """ % {'host': hostname} bad_key_data = good_key_data + """ # missing host ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user4 # insufficient fields ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBalxS0OHm1J3QB7WjtInEhqdMO7cqjqw0yVCfHqb8VU/nXJWQZPJAom8ey3uYWqrjVKuHPSgEaqqtJxwVIeJ5oBDOQbAT9WY4n7+mx8I+bhpdVsZQvtQE3BUgYh0/GUbRgSx+F/1efrwcRHCRb9QO+9DrIg1q2NeY6OR2bSiYA5Q== # too many fields command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user5@%(host)s # unsupported key type ssh-dsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user6@%(host)s # munged data ssh-rsa ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user7@%(host)s """ % {'host': hostname} with open('key_data', 'w') as out: out.write(good_key_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('key_data') try: keys = read_authorized_keys('key_data', logging.getLogger()) for name, key in keys.items(): logging.debug(' %s: %r', name, key) self.assertEqual(sorted(keys.keys()), ['user1@'+hostname, 'user2@'+hostname, 'user3@'+hostname]) finally: os.remove('key_data') # Write and read-back. key_file = 'users.allow' try: write_authorized_keys(keys, key_file) if sys.platform != 'win32' or HAVE_PYWIN32: self.assertTrue(is_private(key_file)) new_keys = read_authorized_keys(key_file) self.assertEqual(len(keys), len(new_keys)) for user in sorted(keys.keys()): pubkey = keys[user] try: new_pubkey = new_keys[user] except KeyError: self.fail('new_keys is missing %r', user) self.assertEqual(new_pubkey.n, pubkey.n) self.assertEqual(new_pubkey.e, pubkey.e) finally: if os.path.exists(key_file): os.remove(key_file) # Try default file, which may or may not exist. try: keys = read_authorized_keys(logger=logging.getLogger()) except RuntimeError: pass # Try nonexistent file. code = "read_authorized_keys('key_data', logging.getLogger())" assert_raises(self, code, globals(), locals(), RuntimeError, "'key_data' does not exist") # Try insecure file. if sys.platform != 'win32': with open('key_data', 'w') as out: out.write(good_key_data) os.chmod('key_data', 0666) try: code = "read_authorized_keys('key_data', logging.getLogger())" assert_raises(self, code, globals(), locals(), RuntimeError, "'key_data' is not private") finally: os.remove('key_data') # Try bad file. with open('key_data', 'w') as out: out.write(bad_key_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('key_data') try: code = "read_authorized_keys('key_data', logging.getLogger())" assert_raises(self, code, globals(), locals(), RuntimeError, "5 errors in 'key_data', check log for details") finally: os.remove('key_data')
def test_authorized_keys(self): logging.debug('') logging.debug('test_authorized_keys') # Try various line formats. hostname = socket.gethostname() good_key_data = """ ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAt9gTm9qX3pKOvFbn8vdkWL/W4kAdtNxRQQXO6QXX7ihuYxv09ZMuqkFPCD1ZxwZNZG0BYstSytPyYQDAGbOglmsjfQ0PRtwDLvK4utGiGLuRsf8ig/cS8NDfSJ/I1B+DBlV1uMaGmzamsFDsavv4Qxf/X50Fl5JTBiPp9W17xkz+JyDCsNMaQd2iSx+GjLbxT/QG2xM9/qrF8bQAAMLdNoKHwVNW+lLXyww6YI9pPj7Tep/dg3xk5Ggf5L6eJGRzmJVMYSfFK+TIX4r49SNddo3Vy/K2H02Yxu6dIBXUTwa+AUC+Mfh5LisAJiM/Oj4NBngWVRgDjN9NH6nQD08R8Q== user1@%(host)s ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAv7QM8MwxkX9yCKIebEH0o14b6Uik3KZnkQo2uF0NyuzDeeZntFym7v0mx7HV4KncjA5Ix2UBw4VKB2virDInO/YKYOC3ZqEJH/CvJkBFggPaZyJyzrEname0+NRXg+PnB2yIDKH0dpwEKVDkwAhEaAqcb9xoahEgXmd4kOmNGylJcwAJhSNqAC9BJO+gAdukGmKodM3nkwKo1BJc2ozqoYar8MYH/FQK8GPBOp4w2LHlm2yXuPB/dqd9/b9N4/ivf5LEthNMn1AnLS37tZIbQ4rSaxLGb72p0iBHSM5oHh1JKDn3mGDKIGxR1cxQ6PuuH6wNB5giNU9U76M4y2QGvw== user2@%(host)s # another user entry ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user3@%(host)s """ % { 'host': hostname } bad_key_data = good_key_data + """ # missing host ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEA4hKDhZ7g/qlNrZuCG4EmYIfeUJLpsJQ4JOHylFwahJEy/A8VQEZpZADynouAhkM4AN96dYfyIRFxLR7EiO9ZSIg5FTF8qcpz2VuV0RBKjwO3R7GD966oRqZ6cz4Otx7LcZfDEVw2ybfe+uYnZZCF69ZpdVkNpg6IUjEqw/VZtpM= user4 # insufficient fields ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIBalxS0OHm1J3QB7WjtInEhqdMO7cqjqw0yVCfHqb8VU/nXJWQZPJAom8ey3uYWqrjVKuHPSgEaqqtJxwVIeJ5oBDOQbAT9WY4n7+mx8I+bhpdVsZQvtQE3BUgYh0/GUbRgSx+F/1efrwcRHCRb9QO+9DrIg1q2NeY6OR2bSiYA5Q== # too many fields command="dump /home",no-pty,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user5@%(host)s # unsupported key type ssh-dsa AAAAB3NzaC1yc2EAAAABIwAAAQEAvD2QBWO4pSgMTkuGo9AqCBNTlAvnDSfKXPnwZZsHSNiWDJcSR+uMBATtdbBfYp039sudx3p+Mhm7IA70G61PiPgRebs8h/XC8gv7bUhDr7tMuG/kngSA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user6@%(host)s # munged data ssh-rsa ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZA61mId65+WtIbTJUnyLyAnRGv1uK4CcpCLAt0SrAbe9l+YAOnit6UQLIaysyrafjgbXgQDC6vFffxP9idJAhPveVV9jVoGvrf6XTAGByRKZzuPlKLIlHunIOOryOLl9FK0IbA7jYeoZ/ESt9mrheECcpAzW4jrEuU0LccN57ODKtT3Mc/sOnBVWIcIJ+5nv2dPsI2fphGrtZuyu+ckIcqhM5ydHHBius8IQ== user7@%(host)s """ % { 'host': hostname } with open('key_data', 'w') as out: out.write(good_key_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('key_data') try: keys = read_authorized_keys('key_data', logging.getLogger()) for name, key in keys.items(): logging.debug(' %s: %r', name, key) self.assertEqual(sorted(keys.keys()), [ 'user1@' + hostname, 'user2@' + hostname, 'user3@' + hostname ]) finally: os.remove('key_data') # Write and read-back. key_file = 'users.allow' try: write_authorized_keys(keys, key_file) if sys.platform != 'win32' or HAVE_PYWIN32: self.assertTrue(is_private(key_file)) new_keys = read_authorized_keys(key_file) self.assertEqual(len(keys), len(new_keys)) for user in sorted(keys.keys()): pubkey = keys[user] try: new_pubkey = new_keys[user] except KeyError: self.fail('new_keys is missing %r', user) self.assertEqual(new_pubkey.n, pubkey.n) self.assertEqual(new_pubkey.e, pubkey.e) finally: if os.path.exists(key_file): os.remove(key_file) # Try default file, which may or may not exist. try: keys = read_authorized_keys(logger=logging.getLogger()) except RuntimeError: pass # Try nonexistent file. code = "read_authorized_keys('key_data', logging.getLogger())" assert_raises(self, code, globals(), locals(), RuntimeError, "'key_data' does not exist") # Try insecure file. if sys.platform != 'win32': with open('key_data', 'w') as out: out.write(good_key_data) os.chmod('key_data', 0666) try: code = "read_authorized_keys('key_data', logging.getLogger())" assert_raises(self, code, globals(), locals(), RuntimeError, "'key_data' is not private") finally: os.remove('key_data') # Try bad file. with open('key_data', 'w') as out: out.write(bad_key_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('key_data') try: code = "read_authorized_keys('key_data', logging.getLogger())" assert_raises(self, code, globals(), locals(), RuntimeError, "5 errors in 'key_data', check log for details") finally: os.remove('key_data')
def test_allowed_hosts(self): logging.debug('') logging.debug('test_allowed_hosts') hostname = socket.gethostname() host_ipv4 = socket.gethostbyname(hostname) dot = host_ipv4.rfind('.') domain_ipv4 = host_ipv4[:dot + 1] good_data = """ # Local host IPv4. %s # Local domain IPv4. %s # Local host name. %s """ % (host_ipv4, domain_ipv4, hostname) gibberish = '$^&*' bad_data = good_data + """ # Gibberish. %s """ % gibberish # Try good data. with open('hosts.allow', 'w') as out: out.write(good_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('hosts.allow') try: allowed_hosts = read_allowed_hosts('hosts.allow') finally: os.remove('hosts.allow') self.assertEqual(len(allowed_hosts), 3) self.assertEqual(allowed_hosts[0], host_ipv4) self.assertEqual(allowed_hosts[1], domain_ipv4) self.assertEqual(allowed_hosts[2], host_ipv4) # Try bad data. with open('hosts.allow', 'w') as out: out.write(bad_data) if sys.platform != 'win32' or HAVE_PYWIN32: make_private('hosts.allow') try: allowed_hosts = read_allowed_hosts('hosts.allow') except RuntimeError as exc: self.assertEqual( str(exc), "1 errors in 'hosts.allow', check log for details") else: expected = 3 try: # This actally resolves in some environments. socket.gethostbyname(gibberish) except socket.gaierror: pass else: expected += 1 self.assertEqual(len(allowed_hosts), expected) self.assertEqual(allowed_hosts[0], host_ipv4) self.assertEqual(allowed_hosts[1], domain_ipv4) self.assertEqual(allowed_hosts[2], host_ipv4) finally: os.remove('hosts.allow') # Check AF_INET addresses. logger = logging.getLogger() self.assertTrue( is_legal_connection((host_ipv4, 0), allowed_hosts, logger)) domain_host = domain_ipv4 + '123' self.assertTrue( is_legal_connection((domain_host, 0), allowed_hosts, logger)) self.assertFalse( is_legal_connection(('0.0.0.0', 0), allowed_hosts, logger)) # Check AF_UNIX address. self.assertTrue(is_legal_connection('/tmp/pipe', allowed_hosts, logger)) # Try nonexistant file. assert_raises(self, "read_allowed_hosts('no-such-file')", globals(), locals(), RuntimeError, "'no-such-file' does not exist") # Try insecure file. if sys.platform != 'win32' or HAVE_PYWIN32: with open('hosts.allow', 'w') as out: out.write('\n') os.chmod('hosts.allow', 0666) try: assert_raises(self, "read_allowed_hosts('hosts.allow')", globals(), locals(), RuntimeError, "'hosts.allow' is not private") finally: os.remove('hosts.allow')