예제 #1
0
 def __init__(self, env, path, swift_source=None):
     self.environ = env
     (version, account, container, obj) = split_path(path, 2, 4, True)
     self.account = account
     self.container = container
     self.obj = obj
     if obj:
         stype = 'object'
         self.headers = {'content-length': 5555,
                         'content-type': 'text/plain'}
     else:
         stype = container and 'container' or 'account'
         self.headers = {'x-%s-object-count' % (stype): 1000,
                         'x-%s-bytes-used' % (stype): 6666}
     if swift_source:
         meta = 'x-%s-meta-fakerequest-swift-source' % stype
         self.headers[meta] = swift_source
예제 #2
0
def split_and_validate_path(request,
                            minsegs=1,
                            maxsegs=None,
                            rest_with_last=False):
    """
    Utility function to split and validate the request path.

    :returns: result of split_path if everything's okay
    :raises: HTTPBadRequest if something's not okay
    """
    try:
        segs = split_path(unquote(request.path), minsegs, maxsegs,
                          rest_with_last)
        validate_device_partition(segs[0], segs[1])
        return segs
    except ValueError as err:
        raise HTTPBadRequest(body=str(err),
                             request=request,
                             content_type='text/plain')
예제 #3
0
def check_copy_from_header(req):
    """
    Validate that the value from x-copy-from header is
    well formatted. We assume the caller ensures that
    x-copy-from header is present in req.headers.

    :param req: HTTP request object
    :returns: A tuple with container name and object name
    :raise: HTTPPreconditionFailed if x-copy-from value
            is not well formatted.
    """
    src_header = unquote(req.headers.get('X-Copy-From'))
    if not src_header.startswith('/'):
        src_header = '/' + src_header
    try:
        return split_path(src_header, 2, 2, True)
    except ValueError:
        raise HTTPPreconditionFailed(
            request=req,
            body='X-Copy-From header must be of the form'
            '<container name>/<object name>')
예제 #4
0
    def __get_service_details(self, metadata, host=''):
        '''
        Get container service details, filesystem, directory
        of container update request.

        :param metadata: object metadata
        :returns: host:       string containing IP and port of container service
                  directory:  directory name
                  file_system:filesystem name
        '''
        account, container, _ = split_path(metadata['name'], 1, 3, True)
        node, filesystem, directory, global_map_version, comp_no = \
            self.__container_ring.get_node(account, container)
        #TODO : Gl interface
        #GET_GLOBAL_MAP
        #component_name = get_path_name()
        #import libraryutils
        #ip,port = get_ip(),get_port()
        #host = '%s:%s' % (ip,port)
        if not host:
            host = '%s:%s' % (node[0]['ip'], node[0]['port'])
        return host, directory, filesystem
예제 #5
0
    def split_path(self, minsegs=1, maxsegs=None, rest_with_last=False):
        """
        Validate and split the Request's path.

        **Examples**::

            ['a'] = split_path('/a')
            ['a', None] = split_path('/a', 1, 2)
            ['a', 'c'] = split_path('/a/c', 1, 2)
            ['a', 'c', 'o/r'] = split_path('/a/c/o/r', 1, 3, True)

        :param minsegs: Minimum number of segments to be extracted
        :param maxsegs: Maximum number of segments to be extracted
        :param rest_with_last: If True, trailing data will be returned as part
                               of last segment.  If False, and there is
                               trailing data, raises ValueError.
        :returns: list of segments with a length of maxsegs (non-existant
                  segments will return as None)
        :raises: ValueError if given an invalid path
        """
        return split_path(
            self.environ.get('SCRIPT_NAME', '') + self.environ['PATH_INFO'],
            minsegs, maxsegs, rest_with_last)
예제 #6
0
    def __get_target_path(self, metadata, file_name):
        '''
        Get targets path of object file and meta file storage.
        :param tmp_dir: tmp directory
        :param file_name: file name
        :returns: data file target path and meta file target path
        '''
        data_file = file_name.split('_')[-1] + '.data'
        meta_file = file_name.split('_')[-1] + '.meta'
        path = metadata['name']
        account, container, obj = split_path(path, 1, 3, True)
        #_, file_system, directory, global_map_version, comp_no = \
        file_system, directory = \
            self.__object_ring.get_node(account, container, obj, only_fs_dir_flag=True)

        acc_dir, cont_dir, obj_dir = directory.split('/')
        data_target_path = join(EXPORT_PATH, file_system, acc_dir, hash_path(account), \
                                cont_dir, hash_path(account, container), \
                                obj_dir, DATADIR, data_file)
        meta_target_path = join(EXPORT_PATH, file_system, acc_dir, hash_path(account), \
                                cont_dir, hash_path(account, container), \
                                obj_dir, METADIR, meta_file)
        return data_target_path, meta_target_path
예제 #7
0
    def get_controller(self, path, method):
        """
        Get the controller to handle a request.

        :param path: path from request
        :returns: tuple of (controller class, path dictionary)

        :raises: ValueError (thrown by split_path) if given invalid path
        """

        version, account, container, obj = split_path(path, 1, 4, True)
        d = dict(version=version,
                 account_name=account,
                 container_name=container,
                 object_name=obj)
        if method == "STOP_SERVICE":
            return StopController, d
        if obj and container and account:
            return ObjectController, d
        elif container and account:
            return ContainerController, d
        elif account and not container and not obj:
            return AccountController, d
        return None, d
예제 #8
0
    def handle_request(self, req):
        """
        Entry point for proxy server.
        Should return a WSGI-style callable (such as swob.Response).

        :param req: swob.Request object
        """
        try:
            if 'swift.trans_id' not in req.environ:
                # if this wasn't set by an earlier middleware, set it now
                trans_id = generate_trans_id(self.trans_id_suffix)
                req.environ['swift.trans_id'] = trans_id
                self.logger.txn_id = trans_id
            self.logger.info(
                'Proxy service received request for %(method)s '
                '%(path)s with %(headers)s (txn: %(trans_id)s)', {
                    'method': req.method,
                    'path': req.path,
                    'headers': req.headers,
                    'trans_id': self.logger.txn_id
                })
            req.headers['x-trans-id'] = req.environ['swift.trans_id']
            self.logger.set_statsd_prefix('proxy-server')
            #if self.stop_service_flag:
            #    self.logger.info('Proxy is going to stop therefore no more request')
            #    return HTTPForbidden(request=req, body='Proxy is going to stop\
            #        therefore no more request')		#TODO would it be HTTPForbidden or something else
            if req.content_length and req.content_length < 0:
                self.logger.increment('errors')
                return HTTPBadRequest(request=req,
                                      body='Invalid Content-Length')

            try:
                if not check_utf8(req.path_info):
                    self.logger.increment('errors')
                    return HTTPPreconditionFailed(
                        request=req, body='Invalid UTF8 or contains NULL')
            except UnicodeError:
                self.logger.increment('errors')
                return HTTPPreconditionFailed(
                    request=req, body='Invalid UTF8 or contains NULL')

            try:
                error_response = constraints.check_non_allowed_headers(req)
                if error_response:
                    return error_response

                obj_list = ''
                if ('bulk-delete' in req.params or 'X-Bulk-Delete' in \
                req.headers) and req.method in ['POST', 'DELETE']:
                    self.logger.debug("*** Bulk delete request ***")
                    cont, obj_list = get_objs_to_delete(req, \
                    self.max_bulk_delete_entries, self.logger)
                    self.logger.debug("Container: %s, Obj list: %s" \
                    % (cont, obj_list))
                    version, account, container = \
                    split_path(req.path, 1, 3, True)
                    if container:
                        container = container.strip('/')
                        if cont != container:
                            self.logger.error("Container in path is different")
                            return HTTPBadRequest(request=req, \
                            body='Container name mismatch')
                        req.path_info = req.path.rstrip('/')
                    else:
                        req.path_info = os.path.join(req.path, cont)
                    req.method = 'BULK_DELETE'
                    req.headers['Content-Length'] = len(str(obj_list))
                    req.body = str(obj_list)

                controller, path_parts = self.get_controller(
                    req.path, req.method)
                p = req.path_info
                if isinstance(p, unicode):
                    p = p.encode('utf-8')
            except ValueError:
                self.logger.increment('errors')
                return HTTPNotFound(request=req)
            if not controller:
                self.logger.increment('errors')
                return HTTPPreconditionFailed(request=req, body='Bad URL')
            if self.deny_host_headers and \
                    req.host.split(':')[0] in self.deny_host_headers:
                return HTTPForbidden(request=req, body='Invalid host header')

            self.logger.set_statsd_prefix('proxy-server.' +
                                          controller.server_type.lower())
            controller = controller(self, **path_parts)
            controller.trans_id = req.environ['swift.trans_id']
            #self.logger.client_ip = get_remote_client(req)
            try:
                handler = getattr(controller, req.method)
                getattr(handler, 'publicly_accessible')
            except AttributeError:
                allowed_methods = getattr(controller, 'allowed_methods', set())
                return HTTPMethodNotAllowed(
                    request=req, headers={'Allow': ', '.join(allowed_methods)})
            if 'swift.authorize' in req.environ:
                # We call authorize before the handler, always. If authorized,
                # we remove the swift.authorize hook so isn't ever called
                # again. If not authorized, we return the denial unless the
                # controller's method indicates it'd like to gather more
                # information and try again later.
                resp = req.environ['swift.authorize'](req)
                if not resp:
                    # No resp means authorized, no delayed recheck required.
                    del req.environ['swift.authorize']
                else:
                    # Response indicates denial, but we might delay the denial
                    # and recheck later. If not delayed, return the error now.
                    if not getattr(handler, 'delay_denial', None):
                        return resp
            # Save off original request method (GET, POST, etc.) in case it
            # gets mutated during handling.  This way logging can display the
            # method the client actually sent.
            req.environ['swift.orig_req_method'] = req.method
            if not req.method == "STOP_SERVICE":
                self.ongoing_operation_list.append(req)
            resp = handler(req)
            self.logger.info(
                'Proxy returning response %(status)s for '
                '%(method)s %(path)s with %(headers)s '
                '(txn: %(trans_id)s)', {
                    'status': resp.status,
                    'method': req.method,
                    'path': req.path,
                    'headers': resp.headers,
                    'trans_id': req.headers['x-trans-id']
                })
            return resp
        except HTTPException as error_response:
            return error_response
        except (Exception, Timeout):
            self.logger.exception(_('ERROR Unhandled exception in request'))
            return HTTPServerError(request=req)
예제 #9
0
    def publish_sample(self, env, bytes_received, bytes_sent):
        req = REQUEST.Request(env)
        try:
            version, account, container, obj = split_path(req.path, 2, 4, True)
        except ValueError:
            return
        now = timeutils.utcnow().isoformat()

        resource_metadata = {
            "path": req.path,
            "version": version,
            "container": container,
            "object": obj,
        }

        for header in self.metadata_headers:
            if header.upper() in req.headers:
                resource_metadata['http_header_%s' % header] = req.headers.get(
                    header.upper())

        with self.pipeline_manager.publisher(
                context.get_admin_context()) as publisher:
            if bytes_received:
                publisher([
                    sample.Sample(name='storage.objects.incoming.bytes',
                                  type=sample.TYPE_DELTA,
                                  unit='B',
                                  volume=bytes_received,
                                  user_id=env.get('HTTP_X_USER_ID'),
                                  project_id=env.get('HTTP_X_TENANT_ID'),
                                  resource_id=account.partition(
                                      self.reseller_prefix)[2],
                                  timestamp=now,
                                  resource_metadata=resource_metadata)
                ])

            if bytes_sent:
                publisher([
                    sample.Sample(name='storage.objects.outgoing.bytes',
                                  type=sample.TYPE_DELTA,
                                  unit='B',
                                  volume=bytes_sent,
                                  user_id=env.get('HTTP_X_USER_ID'),
                                  project_id=env.get('HTTP_X_TENANT_ID'),
                                  resource_id=account.partition(
                                      self.reseller_prefix)[2],
                                  timestamp=now,
                                  resource_metadata=resource_metadata)
                ])

            # publish the event for each request
            # request method will be recorded in the metadata
            resource_metadata['method'] = req.method.lower()
            publisher([
                sample.Sample(name='storage.api.request',
                              type=sample.TYPE_DELTA,
                              unit='request',
                              volume=1,
                              user_id=env.get('HTTP_X_USER_ID'),
                              project_id=env.get('HTTP_X_TENANT_ID'),
                              resource_id=account.partition(
                                  self.reseller_prefix)[2],
                              timestamp=now,
                              resource_metadata=resource_metadata)
            ])
예제 #10
0
    def handle_get_token(self, req):
        """
        Handles the various `request for token and service end point(s)` calls.
        There are various formats to support the various auth servers in the
        past. Examples::

            GET <auth-prefix>/v1/<act>/auth
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>
            GET <auth-prefix>/auth
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <act>:<usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>
            GET <auth-prefix>/v1.0
                X-Auth-User: <act>:<usr>  or  X-Storage-User: <act>:<usr>
                X-Auth-Key: <key>         or  X-Storage-Pass: <key>

        On successful authentication, the response will have X-Auth-Token and
        X-Storage-Token set to the token to use with Swift and X-Storage-URL
        set to the URL to the default Swift cluster to use.

        :param req: The swob.Request to process.
        :returns: swob.Response, 2xx on success with data set as explained
                  above.
        """
        # Validate the request info
        try:
            pathsegs = split_path(req.path_info, 1, 3, True)
        except ValueError:
            self.logger.increment('errors')
            return HTTPNotFound(request=req)
        if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
            account = pathsegs[1]
            user = req.headers.get('x-storage-user')
            if not user:
                user = req.headers.get('x-auth-user')
                if not user or ':' not in user:
                    self.logger.increment('token_denied')
                    return HTTPUnauthorized(request=req,
                                            headers={
                                                'Www-Authenticate':
                                                'Swift realm="%s"' % account
                                            })
                account2, user = user.split(':', 1)
                if account != account2:
                    self.logger.increment('token_denied')
                    return HTTPUnauthorized(request=req,
                                            headers={
                                                'Www-Authenticate':
                                                'Swift realm="%s"' % account
                                            })
            key = req.headers.get('x-storage-pass')
            if not key:
                key = req.headers.get('x-auth-key')
        elif pathsegs[0] in ('auth', 'v1.0'):
            user = req.headers.get('x-auth-user')
            if not user:
                user = req.headers.get('x-storage-user')
            if not user or ':' not in user:
                self.logger.increment('token_denied')
                return HTTPUnauthorized(
                    request=req,
                    headers={'Www-Authenticate': 'Swift realm="unknown"'})
            account, user = user.split(':', 1)
            key = req.headers.get('x-auth-key')
            if not key:
                key = req.headers.get('x-storage-pass')
        else:
            return HTTPBadRequest(request=req)
        if not all((account, user, key)):
            self.logger.increment('token_denied')
            realm = account or 'unknown'
            return HTTPUnauthorized(
                request=req,
                headers={'Www-Authenticate': 'Swift realm="%s"' % realm})
        # Authenticate user
        account_user = account + ':' + user
        if account_user not in self.users:
            self.logger.increment('token_denied')
            return HTTPUnauthorized(
                request=req,
                headers={'Www-Authenticate': 'Swift realm="%s"' % account})
        if self.users[account_user]['key'] != key:
            self.logger.increment('token_denied')
            return HTTPUnauthorized(
                request=req,
                headers={'Www-Authenticate': 'Swift realm="unknown"'})
        account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
        # Get memcache client
        memcache_client = cache_from_env(req.environ)
        if not memcache_client:
            raise Exception('Memcache required')
        # See if a token already exists and hasn't expired
        token = None
        memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user)
        candidate_token = memcache_client.get(memcache_user_key)
        if candidate_token:
            memcache_token_key = \
                '%s/token/%s' % (self.reseller_prefix, candidate_token)
            cached_auth_data = memcache_client.get(memcache_token_key)
            if cached_auth_data:
                expires, old_groups = cached_auth_data
                old_groups = old_groups.split(',')
                new_groups = self._get_user_groups(account, account_user,
                                                   account_id)

                if expires > time() and \
                        set(old_groups) == set(new_groups.split(',')):
                    token = candidate_token
        # Create a new token if one didn't exist
        if not token:
            # Generate new token
            token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
            expires = time() + self.token_life
            groups = self._get_user_groups(account, account_user, account_id)
            # Save token
            memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
            memcache_client.set(memcache_token_key, (expires, groups),
                                time=float(expires - time()))
            # Record the token with the user info for future use.
            memcache_user_key = \
                '%s/user/%s' % (self.reseller_prefix, account_user)
            memcache_client.set(memcache_user_key,
                                token,
                                time=float(expires - time()))
        resp = Response(request=req,
                        headers={
                            'x-auth-token': token,
                            'x-storage-token': token
                        })
        url = self.users[account_user]['url'].replace('$HOST', resp.host_url)
        if self.storage_url_scheme != 'default':
            url = self.storage_url_scheme + ':' + url.split(':', 1)[1]
        resp.headers['x-storage-url'] = url
        return resp
예제 #11
0
    def __call__(self, env, start_response):
        """
        Accepts a standard WSGI application call, authenticating the request
        and installing callback hooks for authorization and ACL header
        validation. For an authenticated request, REMOTE_USER will be set to a
        comma separated list of the user's groups.

        With a non-empty reseller prefix, acts as the definitive auth service
        for just tokens and accounts that begin with that prefix, but will deny
        requests outside this prefix if no other auth middleware overrides it.

        With an empty reseller prefix, acts as the definitive auth service only
        for tokens that validate to a non-empty set of groups. For all other
        requests, acts as the fallback auth service when no other auth
        middleware overrides it.

        Alternatively, if the request matches the self.auth_prefix, the request
        will be routed through the internal auth request handler (self.handle).
        This is to handle granting tokens, etc.
        """
        if self.allow_overrides and env.get('swift.authorize_override', False):
            return self.app(env, start_response)
        if env.get('PATH_INFO', '').startswith(self.auth_prefix):
            return self.handle(env, start_response)
        s3 = env.get('HTTP_AUTHORIZATION')
        token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
        if s3 or (token and token.startswith(self.reseller_prefix)):
            # Note: Empty reseller_prefix will match all tokens.
            groups = self.get_groups(env, token)
            if groups:
                user = groups and groups.split(',', 1)[0] or ''
                trans_id = env.get('swift.trans_id')
                self.logger.debug('User: %s uses token %s (trans_id %s)' %
                                  (user, 's3' if s3 else token, trans_id))
                env['REMOTE_USER'] = groups
                env['swift.authorize'] = self.authorize
                env['swift.clean_acl'] = clean_acl
                if '.reseller_admin' in groups:
                    env['reseller_request'] = True
            else:
                # Unauthorized token
                if self.reseller_prefix and not s3:
                    # Because I know I'm the definitive auth for this token, I
                    # can deny it outright.
                    self.logger.increment('unauthorized')
                    try:
                        vrs, realm, rest = split_path(env['PATH_INFO'], 2, 3,
                                                      True)
                    except ValueError:
                        realm = 'unknown'
                    return HTTPUnauthorized(
                        headers={
                            'Www-Authenticate': 'Swift realm="%s"' % realm
                        })(env, start_response)
                # Because I'm not certain if I'm the definitive auth for empty
                # reseller_prefixed tokens, I won't overwrite swift.authorize.
                elif 'swift.authorize' not in env:
                    env['swift.authorize'] = self.denied_response
        else:
            if self.reseller_prefix:
                # With a non-empty reseller_prefix, I would like to be called
                # back for anonymous access to accounts I know I'm the
                # definitive auth for.
                try:
                    version, rest = split_path(env.get('PATH_INFO', ''), 1, 2,
                                               True)
                except ValueError:
                    version, rest = None, None
                    self.logger.increment('errors')
                if rest and rest.startswith(self.reseller_prefix):
                    # Handle anonymous access to accounts I'm the definitive
                    # auth for.
                    env['swift.authorize'] = self.authorize
                    env['swift.clean_acl'] = clean_acl
                # Not my token, not my account, I can't authorize this request,
                # deny all is a good idea if not already set...
                elif 'swift.authorize' not in env:
                    env['swift.authorize'] = self.denied_response
            # Because I'm not certain if I'm the definitive auth for empty
            # reseller_prefixed accounts, I won't overwrite swift.authorize.
            elif 'swift.authorize' not in env:
                env['swift.authorize'] = self.authorize
                env['swift.clean_acl'] = clean_acl
        return self.app(env, start_response)