def main(): #pragma no cover """ OpenMDAO factory service process. Usage: python objserverfactory.py [--allow-public][--allow-shell][--hosts=filename][--types=filename][--users=filename][--address=address][--port=number][--prefix=name][--tunnel][--resources=filename][--log-host=hostname][--log-port=number][--log-prefix=string] --allow-public: Allows access by anyone from any allowed host. Use with care! --allow-shell: Allows access to :meth:`execute_command` and :meth:`load_model`. Use with care! --hosts: string Filename for allowed hosts specification. Default ``hosts.allow``. Ignored if '--users' is specified. The file should contain IPv4 host addresses, IPv4 domain addresses, or hostnames, one per line. Blank lines are ignored, and '#' marks the start of a comment which continues to the end of the line. For security reasons this file must be accessible only by the user running this server. --types: string Filename for allowed types specification. If not specified then allow types listed by :meth:`factorymanager.get_available_types`. The file should contain one type name per line. --users: string Filename for allowed users specification. Ignored if '--allow-public' is specified. Default is ``~/.ssh/authorized_keys``, other files should be of the same format: each line has ``key-type public-key-data user@host``, where `user` is the username on `host`. `host` will be translated to an IPv4 address and included in the allowed hosts list. Note that this ``user@host`` form is not necessarily enforced by programs which generate keys. For security reasons this file must be accessible only by the user running this server. --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 configuration and stdout/stderr files (default ``server``). --tunnel: Report host IP address but listen for connections from a local SSH tunnel. --resources: string Filename for resource configuration. If not specified then the default of ``~/.openmdao/resources.cfg`` will be used. --log-host: string Hostname to send remote log messages to. --log-port: int Port on `log-host` to send remote log messages to. --log-prefix: string Prefix to apply to remote log messages. Default is ``pid@host``. If ``prefix.key`` exists, it is read for an authorization key string. Otherwise public key authorization and encryption is used. Allowed hosts *must* be specified if `port` is >= 0. Only allowed hosts may connect to the server. Once initialized ``prefix.cfg`` is written with address, port, and public key information. """ parser = optparse.OptionParser() parser.add_option('--address', action='store', type='str', help='Network address to serve.') parser.add_option('--allow-public', action='store_true', default=False, help='Allows access by any user, use with care!') parser.add_option('--allow-shell', action='store_true', default=False, help='Allows potential shell access, use with care!') parser.add_option('--hosts', action='store', type='str', default='hosts.allow', help='Filename for allowed hosts') parser.add_option('--types', action='store', type='str', help='Filename for allowed types') parser.add_option('--users', action='store', type='str', default='~/.ssh/authorized_keys', help='Filename for allowed users') parser.add_option('--port', action='store', type='int', default=0, help='Server port (0 implies next available port)') parser.add_option('--prefix', action='store', default='server', help='Prefix for config and stdout/stderr files') parser.add_option('--tunnel', action='store_true', default=False, help='Report host IP address but listen for connections' ' from a local SSH tunnel') parser.add_option('--resources', action='store', type='str', default=None, help='Filename for resource configuration') parser.add_option('--log-host', action='store', type='str', default=None, help='hostname for remote log messages') parser.add_option('--log-port', action='store', type='int', default=None, help='port for remote log messages') parser.add_option('--log-prefix', action='store', type='str', default=None, help='prefix for remote log messages') options, arguments = parser.parse_args() if arguments: parser.print_help() sys.exit(1) logger = logging.getLogger() logger.setLevel(logging.DEBUG) if options.log_host and options.log_port: install_remote_handler(options.log_host, int(options.log_port), options.log_prefix) server_key = options.prefix+'.key' server_cfg = options.prefix+'.cfg' global _SERVER_CFG _SERVER_CFG = server_cfg # Get authkey. authkey = 'PublicKey' try: with open(server_key, 'r') as inp: authkey = inp.readline().strip() os.remove(server_key) except IOError: pass if options.allow_shell: msg = 'Shell access is ALLOWED' logger.warning(msg) print msg allowed_users = None allowed_hosts = None # Get allowed_users. if options.allow_public: allowed_users = None msg = 'Public access is ALLOWED' logger.warning(msg) print msg if options.port >= 0: # Get allowed_hosts. if os.path.exists(options.hosts): try: allowed_hosts = read_allowed_hosts(options.hosts) except Exception as exc: msg = "Can't read allowed hosts file %r: %s" \ % (options.hosts, exc) logger.error(msg) print msg sys.exit(1) else: msg = 'Allowed hosts file %r does not exist.' % options.hosts logger.error(msg) print msg sys.exit(1) if not allowed_hosts: msg = 'No hosts in allowed hosts file %r.' % options.hosts logger.error(msg) print msg sys.exit(1) else: if os.path.exists(options.users): try: allowed_users = read_authorized_keys(options.users, logger) except Exception as exc: msg = "Can't read allowed users file %r: %s" \ % (options.users, exc) logger.error(msg) print msg sys.exit(1) else: msg = 'Allowed users file %r does not exist.' % options.users logger.error(msg) print msg sys.exit(1) if not allowed_users: msg = 'No users in allowed users file %r.' % options.users logger.error(msg) print msg sys.exit(1) # Get allowed_types. allowed_types = None if options.types: if os.path.exists(options.types): allowed_types = [] with open(options.types, 'r') as inp: line = inp.readline() while line: line = line.strip() if line: allowed_types.append(line) line = inp.readline() else: msg = 'Allowed types file %r does not exist.' % options.types logger.error(msg) print msg sys.exit(1) # Optionally configure resources. if options.resources: # Import here to avoid import loop. from openmdao.main.resource import ResourceAllocationManager as RAM RAM.configure(options.resources) # Get address and create manager. if options.port >= 0: if options.address: # Specify IPv4/hostname. address = (options.address, options.port) else: address = (platform.node(), options.port) else: if options.address: # Specify pipename. address = options.address else: address = None logger.info('Starting FactoryManager %s %r', address, keytype(authkey)) current_process().authkey = authkey bind_address = ('127.0.0.1', options.port) if options.tunnel else address manager = _FactoryManager(bind_address, authkey, name='Factory', allowed_hosts=allowed_hosts, allowed_users=allowed_users, allow_tunneling=options.tunnel) # Set defaults for created ObjServerFactories. # There isn't a good method to propagate these through the manager. ObjServerFactory._address = address ObjServerFactory._allow_shell = options.allow_shell ObjServerFactory._allowed_types = allowed_types ObjServerFactory._allow_tunneling = options.tunnel # Get server, retry if specified address is in use. server = None retries = 0 while server is None: try: server = manager.get_server() except socket.error as exc: if str(exc).find('Address already in use') >= 0: if retries < 10: msg = 'Address %s in use, retrying...' % (address,) logger.debug(msg) print msg time.sleep(5) retries += 1 else: msg = 'Address %s in use, too many retries.' % (address,) logger.error(msg) print msg sys.exit(1) else: raise # Record configuration. real_ip = None if address is None else address[0] write_server_config(server, _SERVER_CFG, real_ip) msg = 'Serving on %s' % (server.address,) logger.info(msg) print msg sys.stdout.flush() # And away we go... signal.signal(signal.SIGTERM, _sigterm_handler) try: server.serve_forever() finally: _cleanup() sys.exit(0)
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 main(): # pragma no cover """ OpenMDAO factory service process. Usage: python objserverfactory.py [--allow-public][--allow-shell][--hosts=filename][--types=filename][--users=filename][--address=address][--port=number][--prefix=name][--tunnel][--resources=filename][--log-host=hostname][--log-port=number][--log-prefix=string] --allow-public: Allows access by anyone from any allowed host. Use with care! --allow-shell: Allows access to :meth:`execute_command` and :meth:`load_model`. Use with care! --hosts: string Filename for allowed hosts specification. Default ``hosts.allow``. Ignored if '--users' is specified. The file should contain IPv4 host addresses, IPv4 domain addresses, or hostnames, one per line. Blank lines are ignored, and '#' marks the start of a comment which continues to the end of the line. For security reasons this file must be accessible only by the user running this server. --types: string Filename for allowed types specification. If not specified then allow types listed by :meth:`factorymanager.get_available_types`. The file should contain one type name per line. --users: string Filename for allowed users specification. Ignored if '--allow-public' is specified. Default is ``~/.ssh/authorized_keys``, other files should be of the same format: each line has ``key-type public-key-data user@host``, where `user` is the username on `host`. `host` will be translated to an IPv4 address and included in the allowed hosts list. Note that this ``user@host`` form is not necessarily enforced by programs which generate keys. For security reasons this file must be accessible only by the user running this server. --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 configuration and stdout/stderr files (default ``server``). --tunnel: Report host IP address but listen for connections from a local SSH tunnel. --resources: string Filename for resource configuration. If not specified then the default of ``~/.openmdao/resources.cfg`` will be used. --log-host: string Hostname to send remote log messages to. --log-port: int Port on `log-host` to send remote log messages to. --log-prefix: string Prefix to apply to remote log messages. Default is ``pid@host``. If ``prefix.key`` exists, it is read for an authorization key string. Otherwise, public key authorization and encryption is used. Allowed hosts *must* be specified if `port` is >= 0. Only allowed hosts may connect to the server. Once initialized ``prefix.cfg`` is written with address, port, and public key information. """ parser = optparse.OptionParser() parser.add_option('--address', action='store', type='str', help='Network address to serve.') parser.add_option('--allow-public', action='store_true', default=False, help='Allows access by any user, use with care!') parser.add_option('--allow-shell', action='store_true', default=False, help='Allows potential shell access, use with care!') parser.add_option('--hosts', action='store', type='str', default='hosts.allow', help='Filename for allowed hosts') parser.add_option('--types', action='store', type='str', help='Filename for allowed types') parser.add_option('--users', action='store', type='str', default='~/.ssh/authorized_keys', help='Filename for allowed users') parser.add_option('--port', action='store', type='int', default=0, help='Server port (0 implies next available port)') parser.add_option('--prefix', action='store', default='server', help='Prefix for config and stdout/stderr files') parser.add_option('--tunnel', action='store_true', default=False, help='Report host IP address but listen for connections' ' from a local SSH tunnel') parser.add_option('--resources', action='store', type='str', default=None, help='Filename for resource configuration') parser.add_option('--log-host', action='store', type='str', default=None, help='hostname for remote log messages') parser.add_option('--log-port', action='store', type='int', default=None, help='port for remote log messages') parser.add_option('--log-prefix', action='store', type='str', default=None, help='prefix for remote log messages') options, arguments = parser.parse_args() if arguments: parser.print_help() sys.exit(1) logger = logging.getLogger() logger.setLevel(logging.DEBUG) if options.log_host and options.log_port: install_remote_handler(options.log_host, int(options.log_port), options.log_prefix) server_key = options.prefix + '.key' server_cfg = options.prefix + '.cfg' global _SERVER_CFG _SERVER_CFG = server_cfg # Get authkey. authkey = 'PublicKey' try: with open(server_key, 'r') as inp: authkey = inp.readline().strip() os.remove(server_key) except IOError: pass if options.allow_shell: msg = 'Shell access is ALLOWED' logger.warning(msg) print msg allowed_users = None allowed_hosts = None # Get allowed_users. if options.allow_public: allowed_users = None msg = 'Public access is ALLOWED' logger.warning(msg) print msg if options.port >= 0: # Get allowed_hosts. if os.path.exists(options.hosts): try: allowed_hosts = read_allowed_hosts(options.hosts) except Exception as exc: msg = "Can't read allowed hosts file %r: %s" \ % (options.hosts, exc) logger.error(msg) print msg sys.exit(1) else: msg = 'Allowed hosts file %r does not exist.' % options.hosts logger.error(msg) print msg sys.exit(1) if not allowed_hosts: msg = 'No hosts in allowed hosts file %r.' % options.hosts logger.error(msg) print msg sys.exit(1) else: if os.path.exists(options.users): try: allowed_users = read_authorized_keys(options.users, logger) except Exception as exc: msg = "Can't read allowed users file %r: %s" \ % (options.users, exc) logger.error(msg) print msg sys.exit(1) else: msg = 'Allowed users file %r does not exist.' % options.users logger.error(msg) print msg sys.exit(1) if not allowed_users: msg = 'No users in allowed users file %r.' % options.users logger.error(msg) print msg sys.exit(1) # Get allowed_types. allowed_types = None if options.types: if os.path.exists(options.types): allowed_types = [] with open(options.types, 'r') as inp: line = inp.readline() while line: line = line.strip() if line: allowed_types.append(line) line = inp.readline() else: msg = 'Allowed types file %r does not exist.' % options.types logger.error(msg) print msg sys.exit(1) # Optionally configure resources. if options.resources: # Import here to avoid import loop. from openmdao.main.resource import ResourceAllocationManager as RAM RAM.configure(options.resources) # Get address and create manager. if options.port >= 0: if options.address: # Specify IPv4/hostname. address = (options.address, options.port) else: address = (platform.node(), options.port) else: if options.address: # Specify pipename. address = options.address else: address = None logger.info('Starting FactoryManager %s %r', address, keytype(authkey)) current_process().authkey = authkey bind_address = ('127.0.0.1', options.port) if options.tunnel else address manager = _FactoryManager(bind_address, authkey, name='Factory', allowed_hosts=allowed_hosts, allowed_users=allowed_users, allow_tunneling=options.tunnel) # Set defaults for created ObjServerFactories. # There isn't a good method to propagate these through the manager. ObjServerFactory._address = address ObjServerFactory._allow_shell = options.allow_shell ObjServerFactory._allowed_types = allowed_types ObjServerFactory._allow_tunneling = options.tunnel # Get server, retry if specified address is in use. server = None retries = 0 while server is None: try: server = manager.get_server() except socket.error as exc: if str(exc).find('Address already in use') >= 0: if retries < 10: msg = 'Address %s in use, retrying...' % (address, ) logger.debug(msg) print msg time.sleep(5) retries += 1 else: msg = 'Address %s in use, too many retries.' % (address, ) logger.error(msg) print msg sys.exit(1) else: raise # Record configuration. real_ip = None if address is None else address[0] write_server_config(server, _SERVER_CFG, real_ip) msg = 'Serving on %s' % (server.address, ) logger.info(msg) print msg sys.stdout.flush() # And away we go... signal.signal(signal.SIGTERM, _sigterm_handler) try: server.serve_forever() finally: _cleanup() sys.exit(0)
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')