def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None, no_scp=False, split_size=0, hide_part_dir=False, auth_timeout=None, negotiation_timeout=0): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS(None, None, authurl=authurl, keystone=keystone, hide_part_dir=hide_part_dir) # unauthorized self.host_key = host_key self.max_children = max_children self.no_scp = no_scp ObjectStorageSFTPRequestHandler.auth_timeout = auth_timeout ObjectStorageSFTPRequestHandler.negotiation_timeout = negotiation_timeout ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) ObjectStorageFD.split_size = split_size
def setUp(self): if not hasattr(self, 'username'): cls = self.__class__ if not all(['OS_API_KEY' in os.environ, 'OS_API_USER' in os.environ, 'OS_AUTH_URL' in os.environ, ]): print "env OS_API_USER/OS_API_KEY/OS_AUTH_URL not found." sys.exit(1) cls.username = os.environ.get('OS_API_USER') cls.api_key = os.environ.get('OS_API_KEY') cls.auth_url = os.environ.get('OS_AUTH_URL') if 'OS_KEYSTONE_REGION_NAME' in os.environ: keystone = {'region_name' : os.environ.get('OS_KEYSTONE_REGION_NAME'), 'tenant_separator' : os.environ.get('OS_KEYSTONE_TENANT_SEPARATOR', ':'), 'service_type' : os.environ.get('OS_KEYSTONE_SERVICE_TYPE', 'object-store'), 'endpoint_type' : os.environ.get('OS_KEYSTONE_ENDPOINT_TYPE', 'publicURL')} cls.cnx = ObjectStorageFS(self.username, self.api_key, self.auth_url, keystone) tenant_name, username = cls.username.split(keystone['tenant_separator'], 1) cls.conn = client.Connection(user=username, tenant_name=tenant_name,key=self.api_key, authurl=self.auth_url, auth_version='2.0') else: cls.cnx = ObjectStorageFS(self.username, self.api_key, self.auth_url) cls.conn = client.Connection(user=self.username, key=self.api_key, authurl=self.auth_url) self.container = "ftpcloudfs_testing" self.cnx.mkdir("/%s" % self.container) self.cnx.chdir("/%s" % self.container)
def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None, no_scp=False, split_size=0, hide_part_dir=False, auth_timeout=None, negotiation_timeout=0, keepalive=0, insecure=False, secopts=None, server_ident=None, storage_policy=None, proxy_protocol=None, rsync_bin=None, large_object_container_suffix=None, fail2ban=None): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS( None, None, authurl=authurl, keystone=keystone, hide_part_dir=hide_part_dir, insecure=insecure, storage_policy=storage_policy) # unauthorized self.host_key = host_key self.max_children = max_children self.no_scp = no_scp self.rsync_bin = rsync_bin self.split_size = split_size self.fail2ban = fail2ban if fail2ban: self.f2b = Fail2ban(fail2ban) ObjectStorageSFTPRequestHandler.auth_timeout = auth_timeout ObjectStorageSFTPRequestHandler.negotiation_timeout = negotiation_timeout ObjectStorageSFTPRequestHandler.keepalive = keepalive ObjectStorageSFTPRequestHandler.secopts = secopts ObjectStorageSFTPRequestHandler.server_ident = server_ident ObjectStorageSFTPRequestHandler.proxy_protocol = proxy_protocol ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) ObjectStorageFD.split_size = split_size ObjectStorageFD.storage_policy = storage_policy ObjectStorageFD.large_object_container_suffix = large_object_container_suffix
class ObjectStorageSFTPServer(ForkingTCPServer, paramiko.ServerInterface): """ Expose a ObjectStorageFS object over SFTP. """ allow_reuse_address = True def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS(None, None, authurl=authurl, keystone=keystone) # unauthorized self.host_key = host_key self.max_children = max_children ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) def check_channel_request(self, kind, chanid): if kind == 'session': return paramiko.OPEN_SUCCEEDED self.log.warning("Channel request denied from %s, kind=%s" \ % (self.client_address, kind)) # all the check_channel_*_request return False by default but # sftp subsystem because of the set_subsystem_handler call in # the ObjectStorageSFTPRequestHandler return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_auth_none(self, username): """Check whether the user can proceed without authentication.""" return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): """Check whether the given public key is valid for authentication.""" return paramiko.AUTH_FAILED def check_auth_password(self, username, password): """Check whether the given password is valid for authentication.""" self.log.info("Auth request (type=password), username=%s, from=%s" \ % (username, self.client_address)) try: if not password: raise EnvironmentError("no password provided") self.fs.authenticate(username, password) except EnvironmentError, e: self.log.warning("%s: Failed to authenticate: %s" % (self.client_address, e)) self.log.error("Authentication failure for %s from %s port %s" % (username, self.client_address[0], self.client_address[1])) return paramiko.AUTH_FAILED self.fs.conn.real_ip = self.client_address[0] self.log.info("%s authenticated from %s" % (username, self.client_address)) return paramiko.AUTH_SUCCESSFUL
def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS(None, None, authurl=authurl, keystone=keystone) # unauthorized self.host_key = host_key self.max_children = max_children ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler)
def __init__( self, address, host_key=None, authurl=None, max_children=20, keystone=None, no_scp=False, split_size=0, hide_part_dir=False, auth_timeout=None, negotiation_timeout=0, keepalive=0, insecure=False, secopts=None, server_ident=None, ): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS( None, None, authurl=authurl, keystone=keystone, hide_part_dir=hide_part_dir, insecure=insecure ) # unauthorized self.host_key = host_key self.max_children = max_children self.no_scp = no_scp ObjectStorageSFTPRequestHandler.auth_timeout = auth_timeout ObjectStorageSFTPRequestHandler.negotiation_timeout = negotiation_timeout ObjectStorageSFTPRequestHandler.keepalive = keepalive ObjectStorageSFTPRequestHandler.secopts = secopts ObjectStorageSFTPRequestHandler.server_ident = server_ident ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) ObjectStorageFD.split_size = split_size
class ObjectStorageSFTPServer(ForkingTCPServer, paramiko.ServerInterface): """ Expose a ObjectStorageFS object over SFTP. """ allow_reuse_address = True def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None, no_scp=False, split_size=0, hide_part_dir=False, auth_timeout=None, negotiation_timeout=0): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS(None, None, authurl=authurl, keystone=keystone, hide_part_dir=hide_part_dir) # unauthorized self.host_key = host_key self.max_children = max_children self.no_scp = no_scp ObjectStorageSFTPRequestHandler.auth_timeout = auth_timeout ObjectStorageSFTPRequestHandler.negotiation_timeout = negotiation_timeout ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) ObjectStorageFD.split_size = split_size def check_channel_request(self, kind, chanid): if kind == 'session': return paramiko.OPEN_SUCCEEDED self.log.warning("Channel request denied from %s, kind=%s" \ % (self.client_address, kind)) # all the check_channel_*_request return False by default but # sftp subsystem because of the set_subsystem_handler call in # the ObjectStorageSFTPRequestHandler return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_channel_exec_request(self, channel, command): """Determine if a shell command will be executed for the client.""" # Parse the command if ' -- ' in command: # scp will use -- to delimit the begining of the unscaped filename # so translate it to something that shelex can manage command = command.replace(' -- ', ' "') + '"' command = shlex.split(command) self.log.debug('check_channel_exec_request %r' % command) try: if command[0] == 'scp': if self.no_scp: self.log.info("scp exec request denied from=%s (scp is disabled)" % (self.client_address,)) return False self.log.info('invoking %r from=%s' % (command, self.client_address)) # handle the command execution SCPHandler(command[1:], channel, self.fs, self.log).start() return True except: self.log.exception("command %r failed from=%s" % (command, self.client_address)) return False return False def check_auth_none(self, username): """Check whether the user can proceed without authentication.""" return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): """Check whether the given public key is valid for authentication.""" return paramiko.AUTH_FAILED def check_auth_password(self, username, password): """Check whether the given password is valid for authentication.""" self.log.info("Auth request (type=password), username=%s, from=%s" \ % (username, self.client_address)) try: if not password: raise EnvironmentError("no password provided") self.fs.authenticate(username, password) except EnvironmentError, e: self.log.warning("%s: Failed to authenticate: %s" % (self.client_address, e)) self.log.error("Authentication failure for %s from %s port %s" % (username, self.client_address[0], self.client_address[1])) return paramiko.AUTH_FAILED self.fs.conn.real_ip = self.client_address[0] self.log.info("%s authenticated from %s" % (username, self.client_address)) return paramiko.AUTH_SUCCESSFUL
class ObjectStorageSFTPServer(ForkingTCPServer, paramiko.ServerInterface): """ Expose a ObjectStorageFS object over SFTP. """ allow_reuse_address = True def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None, no_scp=False, split_size=0, hide_part_dir=False, auth_timeout=None, negotiation_timeout=0, keepalive=0, insecure=False, secopts=None, server_ident=None, storage_policy=None, proxy_protocol=None, rsync_bin=None, large_object_container_suffix=None, fail2ban=None): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS( None, None, authurl=authurl, keystone=keystone, hide_part_dir=hide_part_dir, insecure=insecure, storage_policy=storage_policy) # unauthorized self.host_key = host_key self.max_children = max_children self.no_scp = no_scp self.rsync_bin = rsync_bin self.split_size = split_size self.fail2ban = fail2ban if fail2ban: self.f2b = Fail2ban(fail2ban) ObjectStorageSFTPRequestHandler.auth_timeout = auth_timeout ObjectStorageSFTPRequestHandler.negotiation_timeout = negotiation_timeout ObjectStorageSFTPRequestHandler.keepalive = keepalive ObjectStorageSFTPRequestHandler.secopts = secopts ObjectStorageSFTPRequestHandler.server_ident = server_ident ObjectStorageSFTPRequestHandler.proxy_protocol = proxy_protocol ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) ObjectStorageFD.split_size = split_size ObjectStorageFD.storage_policy = storage_policy ObjectStorageFD.large_object_container_suffix = large_object_container_suffix def check_channel_request(self, kind, chanid): if kind == 'session': return paramiko.OPEN_SUCCEEDED self.log.warning("Channel request denied from %s, kind=%s" \ % (self.client_address, kind)) # all the check_channel_*_request return False by default but # sftp subsystem because of the set_subsystem_handler call in # the ObjectStorageSFTPRequestHandler return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_channel_exec_request(self, channel, command): """Determine if a shell command will be executed for the client.""" # Parse the command if ' -- ' in command: # scp will use -- to delimit the beginning of the unscaped filename # so translate it to something that shelex can manage command = command.replace(' -- ', ' "') + '"' command = shlex.split(command) self.log.debug('check_channel_exec_request %r' % command) try: if command[0] == 'scp': if self.no_scp: self.log.info( "scp exec request denied from=%s (scp is disabled)" % (self.client_address, )) return False self.log.info('invoking %r from=%s' % (command, self.client_address)) # handle the command execution SCPHandler(command[1:], channel, self.fs, self.log).start() return True if command[0] == 'rsync': if self.rsync_bin is None: self.log.info( "rsync exec request denied from=%s (rsync_bin is not set)" % (self.client_address, )) return False self.log.info('invoking %s %r from=%s' % (self.rsync_bin, command, self.client_address)) RsyncHandler(self.rsync_bin, command[1:], channel, self.fs, self.log, self.split_size).start() return True except: self.log.exception("command %r failed from=%s" % (command, self.client_address)) return False return False def check_auth_none(self, username): """Check whether the user can proceed without authentication.""" return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): """Check whether the given public key is valid for authentication.""" return paramiko.AUTH_FAILED def check_auth_password(self, username, password): """Check whether the given password is valid for authentication.""" self.log.info("Auth request (type=password), username=%s, from=%s" \ % (username, self.client_address)) ip = self.client_address[0] if self.fail2ban: try: if self.f2b.get(ip) == 0: self.log.info( "New authentication request from banned address %s" % ip) return paramiko.AUTH_FAILED except Exception as e: self.log.exception("Failed to get %s value from memcache: %s" % (ip, e)) pass try: if not password: raise EnvironmentError("no password provided") self.fs.authenticate(username, password) except EnvironmentError, e: self.log.warning("%s: Failed to authenticate: %s" % (self.client_address, e)) self.log.error( "Authentication failure for %s from %s port %s" % (username, self.client_address[0], self.client_address[1])) if self.fail2ban: try: self.f2b.set(ip) except Exception as e: self.log.exception( "Failed to set new value in memcache: %s" % e) pass return paramiko.AUTH_FAILED self.fs.conn.real_ip = ip self.log.info("%s authenticated from %s" % (username, self.client_address)) return paramiko.AUTH_SUCCESSFUL
class Main(object): def __init__(self): """Parse configuration and CLI options.""" global config_file # look for an alternative configuration file alt_config_file = False # used to show errors before we actually start parsing stuff parser = OptionParser() for arg in sys.argv: if arg == '--config': try: alt_config_file = sys.argv[sys.argv.index(arg) + 1] config_file = alt_config_file except IndexError: pass elif arg.startswith('--config='): _, alt_config_file = arg.split('=', 1) if alt_config_file == '': parser.error("--config option requires an argument") config_file = alt_config_file config = RawConfigParser({ 'auth-url': None, 'insecure': False, 'host-key-file': None, 'bind-address': "127.0.0.1", 'port': 8022, 'server-ident': 'sftpcloudfs_%s' % version, 'memcache': None, 'max-children': "20", 'auth-timeout': "60", 'negotiation-timeout': "0", 'keepalive': "0", 'ciphers': None, 'digests': None, 'log-file': None, 'syslog': 'no', 'verbose': 'no', 'scp-support': 'yes', 'pid-file': None, 'uid': None, 'gid': None, 'split-large-files': "0", 'hide-part-dir': "no", # keystone auth support 'keystone-auth': False, 'keystone-auth-version': '2.0', 'keystone-region-name': None, 'keystone-tenant-separator': default_ks_tenant_separator, 'keystone-domain-separator': '@', 'keystone-service-type': default_ks_service_type, 'keystone-endpoint-type': default_ks_endpoint_type, 'storage-policy': None, 'proxy-protocol': 'no', 'rsync-bin': None, 'large-object-container': 'no', 'large-object-container-suffix': '_segments', 'fail2ban': False, 'ban-time': 600, 'find-time': 600, 'max-retry': 3, }) try: if not config.read(config_file) and alt_config_file: # the default conf file is optional parser.error("failed to read %s" % config_file) except ParsingError as ex: parser.error("failed to read %s: %s" % (config_file, ex.message)) if not config.has_section('sftpcloudfs'): config.add_section('sftpcloudfs') parser = OptionParser(version="%prog " + version, description="This is a SFTP interface to OpenStack " + \ "Object Storage (Swift).", epilog="Contact and support at: %s" % project_url) parser.add_option("-a", "--auth-url", dest="authurl", default=config.get('sftpcloudfs', 'auth-url'), help="Authentication URL") parser.add_option( "--insecure", dest="insecure", action="store_true", default=config.get('sftpcloudfs', 'insecure'), help="Allow to access servers without checking SSL certs") host_key = config.get('sftpcloudfs', 'host-key-file') if host_key: host_key = [x.strip() for x in host_key.split(',')] parser.add_option("-k", "--host-key-file", type="str", dest="host_key", action="append", default=host_key, help="Host key(s) used by the server") parser.add_option("-b", "--bind-address", dest="bind_address", default=config.get('sftpcloudfs', 'bind-address'), help="Address to bind (default: 127.0.0.1)") parser.add_option("-p", "--port", dest="port", type="int", default=config.get('sftpcloudfs', 'port'), help="Port to bind (default: 8022)") parser.add_option("--server-ident", dest="server_ident", type="str", default=config.get('sftpcloudfs', 'server-ident'), help="Server ident to use when sending the SSH banner to the " + \ "client (default: sftpcloudfs_%s)" % version) memcache = config.get('sftpcloudfs', 'memcache') if memcache: memcache = [x.strip() for x in memcache.split(',')] parser.add_option( '--memcache', type="str", dest="memcache", action="append", default=memcache, help="Memcache server(s) to be used for cache (ip:port)") parser.add_option("-l", "--log-file", dest="log_file", default=config.get('sftpcloudfs', 'log-file'), help="Log into provided file") parser.add_option( "-f", "--foreground", dest="foreground", action="store_true", default=False, help="Run in the foreground (don't detach from terminal)") parser.add_option( "--disable-scp", dest="no_scp", action="store_true", default=not config.getboolean('sftpcloudfs', 'scp-support'), help="Disable SCP support (default: enabled)") parser.add_option( "--syslog", dest="syslog", action="store_true", default=config.getboolean('sftpcloudfs', 'syslog'), help="Enable logging to system logger (daemon facility)") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=config.getboolean('sftpcloudfs', 'verbose'), help="Show detailed information on logging") parser.add_option('--pid-file', type="str", dest="pid_file", default=config.get('sftpcloudfs', 'pid-file'), help="Full path to the pid file location") parser.add_option( '--uid', dest="uid", default=config.get('sftpcloudfs', 'uid'), help="UID to drop the privileges to when in daemon mode") parser.add_option( '--gid', dest="gid", default=config.get('sftpcloudfs', 'gid'), help="GID to drop the privileges to when in daemon mode") parser.add_option('--keystone-auth', action="store_true", dest="keystone", default=config.get('sftpcloudfs', 'keystone-auth'), help="Use Keystone auth (requires keystoneclient)") parser.add_option( '--keystone-auth-version', type="str", dest="auth_version", default=config.get('sftpcloudfs', 'keystone-auth-version'), help="Identity API version to be used (default: 2.0)") parser.add_option('--keystone-region-name', type="str", dest="region_name", default=config.get('sftpcloudfs', 'keystone-region-name'), help="Region name to be used in Keystone auth") parser.add_option('--keystone-tenant-separator', type="str", dest="tenant_separator", default=config.get('sftpcloudfs', 'keystone-tenant-separator'), help="Character used to separate tenant_name/username in Keystone auth, " + \ "default: TENANT%sUSERNAME" % default_ks_tenant_separator) parser.add_option('--keystone-domain-separator', type="str", dest="domain_separator", default=config.get('sftpcloudfs', 'keystone-domain-separator'), help="Character used to separate project_name/project_domain_name " + \ "and username/user_domain_name in Keystone auth v3 (default: @)") parser.add_option( '--keystone-service-type', type="str", dest="service_type", default=config.get('sftpcloudfs', 'keystone-service-type'), help="Service type to be used in Keystone auth, default: %s" % default_ks_service_type) parser.add_option( '--keystone-endpoint-type', type="str", dest="endpoint_type", default=config.get('sftpcloudfs', 'keystone-endpoint-type'), help="Endpoint type to be used in Keystone auth, default: %s" % default_ks_endpoint_type) parser.add_option('--config', type="str", dest="config", default=config_file, help="Use an alternative configuration file") parser.add_option("--storage-policy", type="str", dest="storage_policy", default=config.get('sftpcloudfs', 'storage-policy'), help="Swift storage policy to be used") parser.add_option("--proxy-protocol", action="store_true", dest="proxy_protocol", default=config.getboolean('sftpcloudfs', 'proxy-protocol'), help="Enable the Proxy protocol header parser") parser.add_option("--rsync-bin", type="str", dest="rsync_bin", default=config.get('sftpcloudfs', 'rsync-bin'), help="Custom rsync binary to be used") parser.add_option('--large-object-container', action="store_true", dest="large_object_container", default=config.getboolean('sftpcloudfs', 'large-object-container'), help="Enable large object container support") parser.add_option( '--large-object-container-suffix', type="str", dest="large_object_container_suffix", default=config.get('sftpcloudfs', 'large-object-container-suffix'), help="Large object container suffix (default: '_segments'") parser.add_option('--fail2ban', action="store_true", dest="fail2ban", default=config.get('sftpcloudfs', 'fail2ban'), help="Enable fail2ban feature (requires memcache)") parser.add_option('--ban-time', type="int", dest="ban_time", default=config.get('sftpcloudfs', 'ban-time'), help="Ban duration in seconds (default: 600)") parser.add_option( '--find-time', type="int", dest="find_time", default=config.get('sftpcloudfs', 'find-time'), help= "Duration in seconds before counter reset if no match is found (default: 600)" ) parser.add_option( '--max-retry', type="int", dest="max_retry", default=config.get('sftpcloudfs', 'max-retry'), help= "Number of matches before triggering the ban action (default: 3)") (options, args) = parser.parse_args() # required parameters if not options.authurl: parser.error("No auth-url provided") if not options.host_key: parser.error("No host-key-file provided") self.host_key = [] try: [ self.host_key.append(self._get_pkey_object(k)) for k in options.host_key ] except (IOError, paramiko.SSHException), e: parser.error("host-key-file: %s" % e) if options.memcache: ObjectStorageFS.memcache_hosts = options.memcache try: ObjectStorageFS(None, None, None) except (ValueError, TypeError): parser.error( "memcache: invalid server address, ip:port expected") if options.pid_file: self.pidfile = PIDFile(options.pid_file) if self.pidfile.is_locked(): parser.error( "pid-file found: %s\nIs the server already running?" % options.pid_file) else: self.pidfile = None try: options.max_children = int( config.get('sftpcloudfs', 'max-children')) except ValueError: parser.error('max-children: invalid value, integer expected') try: options.auth_timeout = int( config.get('sftpcloudfs', 'auth-timeout')) except ValueError: parser.error('auth-timeout: invalid value, integer expected') if options.auth_timeout <= 0: parser.error('auth-timeout: invalid value') try: options.negotiation_timeout = int( config.get('sftpcloudfs', 'negotiation-timeout')) except ValueError: parser.error( 'negotiation-timeout: invalid value, integer expected') if options.negotiation_timeout < 0: parser.error('negotiation-timeout: invalid value') try: options.keepalive = int(config.get('sftpcloudfs', 'keepalive')) except ValueError: parser.error('keepalive: invalid value, integer expected') if options.keepalive < 0: parser.error('keepalive: invalid value') options.secopts = {} ciphers = config.get('sftpcloudfs', 'ciphers') if ciphers: options.secopts["ciphers"] = [ x.strip() for x in ciphers.split(',') ] digests = config.get('sftpcloudfs', 'digests') if digests: options.secopts["digests"] = [ x.strip() for x in digests.split(',') ] try: options.split_size = int( config.get('sftpcloudfs', 'split-large-files')) * 2**20 except ValueError: parser.error('split-large-files: invalid size, integer expected') options.hide_part_dir = config.getboolean('sftpcloudfs', 'hide-part-dir') if options.keystone: keystone_keys = ('auth_version', 'region_name', 'tenant_separator', 'domain_separator', 'service_type', 'endpoint_type') options.keystone = dict( (key, getattr(options, key)) for key in keystone_keys) if options.uid: try: options.uid = int(options.uid) except ValueError: try: options.uid = pwd.getpwnam(options.uid).pw_uid except KeyError: parser.error("uid: Invalid uid: %s" % options.uid) if options.gid: try: options.gid = int(options.gid) except ValueError: try: options.gid = pwd.getpwnam(options.gid).pw_gid except KeyError: parser.error("gid: Invalid gid: %s" % options.gid) if config.getboolean('sftpcloudfs', 'large-object-container'): try: options.large_object_container_suffix = config.get( 'sftpcloudfs', 'large-object-container-suffix') except ValueError: parser.error( 'large-object-container-suffix: invalid value, string expected' ) else: options.large_object_container_suffix = None if options.fail2ban: if not options.memcache: parser.error('memcache is mandatory to use fail2ban feature') fail2ban_keys = ('ban_time', 'find_time', 'max_retry', 'memcache') options.fail2ban = dict( (key, getattr(options, key)) for key in fail2ban_keys) self.options = options
class ObjectStorageSFTPServer(ForkingTCPServer, paramiko.ServerInterface): """ Expose a ObjectStorageFS object over SFTP. """ allow_reuse_address = True def __init__(self, address, host_key=None, authurl=None, max_children=20, keystone=None, no_scp=False, split_size=0, hide_part_dir=False, auth_timeout=None, negotiation_timeout=0): self.log = paramiko.util.get_logger("paramiko") self.log.debug("%s: start server" % self.__class__.__name__) self.fs = ObjectStorageFS(None, None, authurl=authurl, keystone=keystone, hide_part_dir=hide_part_dir) # unauthorized self.host_key = host_key self.max_children = max_children self.no_scp = no_scp ObjectStorageSFTPRequestHandler.auth_timeout = auth_timeout ObjectStorageSFTPRequestHandler.negotiation_timeout = negotiation_timeout ForkingTCPServer.__init__(self, address, ObjectStorageSFTPRequestHandler) ObjectStorageFD.split_size = split_size def check_channel_request(self, kind, chanid): if kind == 'session': return paramiko.OPEN_SUCCEEDED self.log.warning("Channel request denied from %s, kind=%s" \ % (self.client_address, kind)) # all the check_channel_*_request return False by default but # sftp subsystem because of the set_subsystem_handler call in # the ObjectStorageSFTPRequestHandler return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED def check_channel_exec_request(self, channel, command): """Determine if a shell command will be executed for the client.""" # Parse the command if ' -- ' in command: # scp will use -- to delimit the begining of the unscaped filename # so translate it to something that shelex can manage command = command.replace(' -- ', ' "') + '"' command = shlex.split(command) self.log.debug('check_channel_exec_request %r' % command) try: if command[0] == 'scp': if self.no_scp: self.log.info( "scp exec request denied from=%s (scp is disabled)" % (self.client_address, )) return False self.log.info('invoking %r from=%s' % (command, self.client_address)) # handle the command execution SCPHandler(command[1:], channel, self.fs, self.log).start() return True except: self.log.exception("command %r failed from=%s" % (command, self.client_address)) return False return False def check_auth_none(self, username): """Check whether the user can proceed without authentication.""" return paramiko.AUTH_FAILED def check_auth_publickey(self, username, key): """Check whether the given public key is valid for authentication.""" return paramiko.AUTH_FAILED def check_auth_password(self, username, password): """Check whether the given password is valid for authentication.""" self.log.info("Auth request (type=password), username=%s, from=%s" \ % (username, self.client_address)) try: if not password: raise EnvironmentError("no password provided") result = re.search('(@|^acc-)', username) if (result is None): raise EnvironmentError("bogus username") self.fs.authenticate(username, password) except EnvironmentError, e: self.log.warning("%s: Failed to authenticate: %s" % (self.client_address, e)) self.log.error( "Authentication failure for %s from %s port %s" % (username, self.client_address[0], self.client_address[1])) return paramiko.AUTH_FAILED self.fs.conn.real_ip = self.client_address[0] self.log.info("%s authenticated from %s" % (username, self.client_address)) return paramiko.AUTH_SUCCESSFUL
class Main(object): def __init__(self): """Parse configuration and CLI options.""" global config_file # look for an alternative configuration file alt_config_file = False # used to show errors before we actually start parsing stuff parser = OptionParser() for arg in sys.argv: if arg == '--config': try: alt_config_file = sys.argv[sys.argv.index(arg) + 1] config_file = alt_config_file except IndexError: pass elif arg.startswith('--config='): _, alt_config_file = arg.split('=', 1) if alt_config_file == '': parser.error("--config option requires an argument") config_file = alt_config_file config = RawConfigParser({ 'auth-url': None, 'host-key-file': None, 'bind-address': "127.0.0.1", 'port': 8022, 'memcache': None, 'max-children': "40", 'auth-timeout': "30", 'negotiation-timeout': "30", 'log-file': None, 'syslog': 'no', 'verbose': 'no', 'scp-support': 'yes', 'pid-file': None, 'uid': None, 'gid': None, 'split-large-files': "1073741824", 'hide-part-dir': "no", # keystone auth 2.0 support 'keystone-auth': False, 'keystone-region-name': None, 'keystone-tenant-separator': default_ks_tenant_separator, 'keystone-service-type': default_ks_service_type, 'keystone-endpoint-type': default_ks_endpoint_type, }) if not config.read(config_file) and alt_config_file: # the default conf file is optional parser.error("failed to read %s" % config_file) if not config.has_section('sftpcloudfs'): config.add_section('sftpcloudfs') parser = OptionParser(version="%prog " + version, description="This is a SFTP interface to OpenStack " + \ "Object Storage (Swift).", epilog="Contact and support at: %s" % project_url) parser.add_option("-a", "--auth-url", dest="authurl", default=config.get('sftpcloudfs', 'auth-url'), help="Authentication URL") parser.add_option("-k", "--host-key-file", dest="host_key", default=config.get('sftpcloudfs', 'host-key-file'), help="Host RSA key used by the server") parser.add_option("-b", "--bind-address", dest="bind_address", default=config.get('sftpcloudfs', 'bind-address'), help="Address to bind (default: 127.0.0.1)") parser.add_option("-p", "--port", dest="port", type="int", default=config.get('sftpcloudfs', 'port'), help="Port to bind (default: 8022)") memcache = config.get('sftpcloudfs', 'memcache') if memcache: memcache = [x.strip() for x in memcache.split(',')] parser.add_option( '--memcache', type="str", dest="memcache", action="append", default=memcache, help="Memcache server(s) to be used for cache (ip:port)") parser.add_option("-l", "--log-file", dest="log_file", default=config.get('sftpcloudfs', 'log-file'), help="Log into provided file") parser.add_option( "-f", "--foreground", dest="foreground", action="store_true", default=False, help="Run in the foreground (don't detach from terminal)") parser.add_option( "--disable-scp", dest="no_scp", action="store_true", default=not config.getboolean('sftpcloudfs', 'scp-support'), help="Disable SCP support (default: enabled)") parser.add_option( "--syslog", dest="syslog", action="store_true", default=config.getboolean('sftpcloudfs', 'syslog'), help="Enable logging to system logger (daemon facility)") parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=config.getboolean('sftpcloudfs', 'verbose'), help="Show detailed information on logging") parser.add_option('--pid-file', type="str", dest="pid_file", default=config.get('sftpcloudfs', 'pid-file'), help="Pid file location when in daemon mode") parser.add_option( '--uid', type="int", dest="uid", default=config.get('sftpcloudfs', 'uid'), help="UID to drop the privileges to when in daemon mode") parser.add_option( '--gid', type="int", dest="gid", default=config.get('sftpcloudfs', 'gid'), help="GID to drop the privileges to when in daemon mode") parser.add_option( '--keystone-auth', action="store_true", dest="keystone", default=config.get('sftpcloudfs', 'keystone-auth'), help="Use auth 2.0 (Keystone, requires keystoneclient)") parser.add_option('--keystone-region-name', type="str", dest="region_name", default=config.get('sftpcloudfs', 'keystone-region-name'), help="Region name to be used in auth 2.0") parser.add_option('--keystone-tenant-separator', type="str", dest="tenant_separator", default=config.get('sftpcloudfs', 'keystone-tenant-separator'), help="Character used to separate tenant_name/username in auth 2.0, " + \ "default: TENANT%sUSERNAME" % default_ks_tenant_separator) parser.add_option( '--keystone-service-type', type="str", dest="service_type", default=config.get('sftpcloudfs', 'keystone-service-type'), help="Service type to be used in auth 2.0, default: %s" % default_ks_service_type) parser.add_option( '--keystone-endpoint-type', type="str", dest="endpoint_type", default=config.get('sftpcloudfs', 'keystone-endpoint-type'), help="Endpoint type to be used in auth 2.0, default: %s" % default_ks_endpoint_type) parser.add_option('--config', type="str", dest="config", default=config_file, help="Use an alternative configuration file") (options, args) = parser.parse_args() # required parameters if not options.authurl: parser.error("No auth-url provided") if not options.host_key: parser.error("No host-key-file provided") try: self.host_key = paramiko.RSAKey(filename=options.host_key) except (IOError, paramiko.SSHException), e: parser.error("host-key-file: %s" % e) if not options.pid_file: options.pid_file = "%s/%s.pid" % (tempfile.gettempdir(), __package__) if options.memcache: ObjectStorageFS.memcache_hosts = options.memcache try: ObjectStorageFS(None, None, None) except (ValueError, TypeError): parser.error( "memcache: invalid server address, ip:port expected") self.pidfile = PIDFile(options.pid_file) if self.pidfile.is_locked(): parser.error("pid-file found: %s\nIs the server already running?" % options.pid_file) try: options.max_children = int( config.get('sftpcloudfs', 'max-children')) except ValueError: parser.error('max-children: invalid value, integer expected') try: options.auth_timeout = int( config.get('sftpcloudfs', 'auth-timeout')) except ValueError: parser.error('auth-timeout: invalid value, integer expected') if options.auth_timeout <= 0: parser.error('auth-timeout: invalid value') try: options.negotiation_timeout = int( config.get('sftpcloudfs', 'negotiation-timeout')) except ValueError: parser.error( 'negotiation-timeout: invalid value, integer expected') if options.negotiation_timeout < 0: parser.error('negotiation-timeout: invalid value') try: options.split_size = int( config.get('sftpcloudfs', 'split-large-files')) * 10**6 except ValueError: parser.error('split-large-files: invalid size, integer expected') options.hide_part_dir = config.getboolean('sftpcloudfs', 'hide-part-dir') if options.keystone: keystone_keys = ('region_name', 'tenant_separator', 'service_type', 'endpoint_type') options.keystone = dict( (key, getattr(options, key)) for key in keystone_keys) self.options = options