def auth_callback_same_account(req): try: _ver, acc, _rest = req.split_path(2, 3, True) except ValueError: return HTTPUnauthorized(request=req) if acc == account_to_match: return None else: return HTTPUnauthorized(request=req)
def auth_callback_same_container(req): try: _ver, acc, con, _rest = req.split_path(3, 4, True) except ValueError: return HTTPUnauthorized(request=req) if acc == account_to_match and con == container_to_match: return None else: return HTTPUnauthorized(request=req)
def __call__(self, env, start_response): if env['REQUEST_METHOD'] == 'GET': if self.status == 200: start_response(Response().status, [('Content-Type', 'text/xml')]) json_pattern = [ '"name":%s', '"last_modified":%s', '"hash":%s', '"bytes":%s' ] json_pattern = '{' + ','.join(json_pattern) + '}' json_out = [] for b in self.objects: name = simplejson.dumps(b[0]) time = simplejson.dumps(b[1]) json_out.append(json_pattern % (name, time, b[2], b[3])) account_list = '[' + ','.join(json_out) + ']' return account_list elif self.status == 401: start_response(HTTPUnauthorized().status, []) elif self.status == 403: start_response(HTTPForbidden().status, []) elif self.status == 404: start_response(HTTPNotFound().status, []) else: start_response(HTTPBadRequest().status, []) elif env['REQUEST_METHOD'] == 'PUT': if self.status == 201: start_response(HTTPCreated().status, []) elif self.status == 401: start_response(HTTPUnauthorized().status, []) elif self.status == 403: start_response(HTTPForbidden().status, []) elif self.status == 202: start_response(HTTPAccepted().status, []) else: start_response(HTTPBadRequest().status, []) elif env['REQUEST_METHOD'] == 'POST': if self.status == 204: start_response(HTTPNoContent().status, []) else: start_response(HTTPBadRequest().status, []) elif env['REQUEST_METHOD'] == 'DELETE': if self.status == 204: start_response(HTTPNoContent().status, []) elif self.status == 401: start_response(HTTPUnauthorized().status, []) elif self.status == 403: start_response(HTTPForbidden().status, []) elif self.status == 404: start_response(HTTPNotFound().status, []) elif self.status == 409: start_response(HTTPConflict().status, []) else: start_response(HTTPBadRequest().status, []) return []
def GETorHEAD(self, req): """Handler for HTTP GET/HEAD requests.""" """ Handles requests to /info Should return a WSGI-style callable (such as swob.Response). :param req: swob.Request object """ if not self.expose_info: return HTTPForbidden(request=req) admin_request = False sig = req.params.get('swiftinfo_sig', '') expires = req.params.get('swiftinfo_expires', '') if sig != '' or expires != '': admin_request = True if not self.admin_key: return HTTPForbidden(request=req) try: expires = int(expires) except ValueError: return HTTPUnauthorized(request=req) if expires < time(): return HTTPUnauthorized(request=req) valid_sigs = [] for method in self.allowed_hmac_methods[req.method]: valid_sigs.append( get_hmac(method, '/info', expires, self.admin_key)) # While it's true that any() will short-circuit, this doesn't # affect the timing-attack resistance since the only way this will # short-circuit is when a valid signature is passed in. is_valid_hmac = any( streq_const_time(valid_sig, sig) for valid_sig in valid_sigs) if not is_valid_hmac: return HTTPUnauthorized(request=req) headers = {} if 'Origin' in req.headers: headers['Access-Control-Allow-Origin'] = req.headers['Origin'] headers['Access-Control-Expose-Headers'] = ', '.join( ['x-trans-id']) info = json.dumps( get_swift_info(admin=admin_request, disallowed_sections=self.disallowed_sections)) return HTTPOk(request=req, headers=headers, body=info, content_type='application/json; charset=UTF-8')
def __call__(self, env, start_response): req = Request(env) if env['REQUEST_METHOD'] == 'GET' or env['REQUEST_METHOD'] == 'HEAD': if self.status == 200: if 'HTTP_RANGE' in env: resp = Response(request=req, body=self.object_body, conditional_response=True) return resp(env, start_response) start_response( Response(request=req).status, self.response_headers.items()) if env['REQUEST_METHOD'] == 'GET': return self.object_body elif self.status == 401: start_response(HTTPUnauthorized(request=req).status, []) elif self.status == 403: start_response(HTTPForbidden(request=req).status, []) elif self.status == 404: start_response(HTTPNotFound(request=req).status, []) else: start_response(HTTPBadRequest(request=req).status, []) elif env['REQUEST_METHOD'] == 'PUT': if self.status == 201: start_response( HTTPCreated(request=req).status, [('etag', self.response_headers['etag'])]) elif self.status == 401: start_response(HTTPUnauthorized(request=req).status, []) elif self.status == 403: start_response(HTTPForbidden(request=req).status, []) elif self.status == 404: start_response(HTTPNotFound(request=req).status, []) elif self.status == 413: start_response( HTTPRequestEntityTooLarge(request=req).status, []) else: start_response(HTTPBadRequest(request=req).status, []) elif env['REQUEST_METHOD'] == 'DELETE': if self.status == 204: start_response(HTTPNoContent(request=req).status, []) elif self.status == 401: start_response(HTTPUnauthorized(request=req).status, []) elif self.status == 403: start_response(HTTPForbidden(request=req).status, []) elif self.status == 404: start_response(HTTPNotFound(request=req).status, []) else: start_response(HTTPBadRequest(request=req).status, []) return []
def GETorHEAD(self, req): """Handler for HTTP GET/HEAD requests.""" """ Handles requests to /info Should return a WSGI-style callable (such as swob.Response). :param req: swob.Request object """ if not self.expose_info: return HTTPForbidden(request=req) admin_request = False sig = req.params.get('swiftinfo_sig', '') expires = req.params.get('swiftinfo_expires', '') if sig != '' or expires != '': admin_request = True if not self.admin_key: return HTTPForbidden(request=req) try: expires = int(expires) except ValueError: return HTTPUnauthorized(request=req) if expires < time(): return HTTPUnauthorized(request=req) valid_sigs = [] for method in self.allowed_hmac_methods[req.method]: valid_sigs.append( get_hmac(method, '/info', expires, self.admin_key)) if sig not in valid_sigs: return HTTPUnauthorized(request=req) headers = {} if 'Origin' in req.headers: headers['Access-Control-Allow-Origin'] = req.headers['Origin'] headers['Access-Control-Expose-Headers'] = ', '.join( ['x-trans-id']) info = json.dumps( get_swift_info(admin=admin_request, disallowed_sections=self.disallowed_sections)) return HTTPOk(request=req, headers=headers, body=info, content_type='application/json; charset=UTF-8')
def POST(self): """ POST handler on Proxy Deals with storlet ACL updates """ # Get the current container's ACL # We perform a sub request rather than get_container_info # since get_container_info bypasses authorization, and we # prefer to be on the safe side. sub_req = make_subrequest(self.request.environ, 'HEAD', self.path, agent=self.agent) sub_resp = sub_req.get_response(self.app) if sub_resp.status_int != 204: self.logger.info("Failed to retrieve container metadata") return HTTPUnauthorized(('Unauthorized to get or modify ' 'the container ACL')) # Add the requested ACL read_acl = sub_resp.headers.get("X-Container-Read") if read_acl: new_read_acl = ','.join([read_acl, self.acl_string]) else: new_read_acl = self.acl_string self.request.headers['X-Container-Read'] = new_read_acl resp = self.request.get_response(self.app) return resp
def handle_shared(self, version, account, shared_account, shared_container, env): if not account: return HTTPUnauthorized() email = 'shared' if self.whitelist_url: acc_id = get_account_from_whitelist(self.whitelist_url, self.app, unquote(shared_account), self.logger, env) if acc_id and acc_id.startswith(self.google_prefix): email = unquote(shared_account) shared_account = acc_id shared = retrieve_metadata(self.app, self.version, account, 'shared', env) if not shared: shared = {} if version in self.shared_container_add: shared['%s/%s' % (shared_account, shared_container)] = email elif version in self.shared_container_remove: try: del shared['%s/%s' % (shared_account, shared_container)] except KeyError: return HTTPNotFound( body='Could not remove shared container %s/%s' % (shared_account, shared_container)) if store_metadata(self.app, self.version, account, 'shared', shared, env): return Response( body='Successfully handled shared container %s/%s' % (shared_account, shared_container)) return HTTPNotFound(body='Could not handle shared container %s/%s' % (shared_account, shared_container))
def denied_response(self, req): if req.remote_user: self.logger.increment('forbidden') return HTTPForbidden(request=req) else: self.logger.increment('unauthorized') return HTTPUnauthorized(request=req)
def handle_login(self, req, code, state): self.storage_driver = LiteAuthStorage(req.environ, self.prefix) oauth_client = self.provider.create_for_token(self.conf, code) token = oauth_client.access_token if not token: req.response = HTTPUnauthorized(request=req) return req.response user_info = oauth_client.userinfo if not user_info: req.response = HTTPForbidden(request=req) return req.response account_id = '%s:%s' % (self.prefix + user_info.get('id'), user_info.get('email')) self.storage_driver.store_id(account_id, token, oauth_client.expires_in) return Response(request=req, status=302, headers={ 'x-auth-token': token, 'x-storage-token': token, 'x-auth-token-expires': oauth_client.expires_in, 'x-storage-url': self.auth_endpoint, 'location': '%s%s?account=%s' % (self.service_domain, state or '/', account_id) })
def __call__(self, env, start_response): """ Main hook into the WSGI paste.deploy filter/app pipeline. :param env: The WSGI environment dict. :param start_response: The WSGI start_response hook. :returns: Response as per WSGI. """ if env['REQUEST_METHOD'] == 'POST': try: content_type, attrs = \ _parse_attrs(env.get('CONTENT_TYPE') or '') if content_type == 'multipart/form-data' and \ 'boundary' in attrs: http_user_agent = "%s FormPost" % (env.get( 'HTTP_USER_AGENT', '')) env['HTTP_USER_AGENT'] = http_user_agent.strip() status, headers, body = self._translate_form( env, attrs['boundary']) start_response(status, headers) return body except (FormInvalid, EOFError) as err: body = 'FormPost: %s' % err start_response('400 Bad Request', (('Content-Type', 'text/plain'), ('Content-Length', str(len(body))))) return [body] except FormUnauthorized as err: message = 'FormPost: %s' % str(err).title() return HTTPUnauthorized(body=message)(env, start_response) return self.app(env, start_response)
def _process_delete(self, delete_path, obj_name, env, resp_dict, failed_files, failed_file_response, retry=0): delete_obj_req = Request.blank(delete_path, env) resp = delete_obj_req.get_response(self.app) if resp.status_int // 100 == 2: resp_dict['Number Deleted'] += 1 elif resp.status_int == HTTP_NOT_FOUND: resp_dict['Number Not Found'] += 1 elif resp.status_int == HTTP_UNAUTHORIZED: failed_files.append([quote(obj_name), HTTPUnauthorized().status]) elif resp.status_int == HTTP_CONFLICT and \ self.retry_count > 0 and self.retry_count > retry: retry += 1 sleep(self.retry_interval**retry) self._process_delete(delete_path, obj_name, env, resp_dict, failed_files, failed_file_response, retry) else: if resp.status_int // 100 == 5: failed_file_response['type'] = HTTPBadGateway failed_files.append([quote(obj_name), resp.status])
def _process_delete(self, resp, pile, obj_name, resp_dict, failed_files, failed_file_response, retry=0): if resp.status_int // 100 == 2: resp_dict['Number Deleted'] += 1 elif resp.status_int == HTTP_NOT_FOUND: resp_dict['Number Not Found'] += 1 elif resp.status_int == HTTP_UNAUTHORIZED: failed_files.append( [wsgi_quote(str_to_wsgi(obj_name)), HTTPUnauthorized().status]) elif resp.status_int == HTTP_CONFLICT and pile and \ self.retry_count > 0 and self.retry_count > retry: retry += 1 sleep(self.retry_interval**retry) delete_obj_req = Request.blank(resp.environ['PATH_INFO'], resp.environ) def _retry(req, app, obj_name, retry): return req.get_response(app), obj_name, retry pile.spawn(_retry, delete_obj_req, self.app, obj_name, retry) else: if resp.status_int // 100 == 5: failed_file_response['type'] = HTTPBadGateway failed_files.append( [wsgi_quote(str_to_wsgi(obj_name)), resp.status])
def __call__(self, env, start_response): """ Accepts a standard WSGI application call. TODO(darrell): add support for Swift v1 authen/authz TODO(darrell): add support for X-Auth-Token header Currently only supports authentication (via swift3 callback) and authorization for S3 API requests. """ # TODO(darrell): sniff out and handle Swift v1 auth requests # check-and-maybe-reload-db self.reload_confs() s3 = env.get('swift3.auth_details') # TODO(darrell): look for and do something with X-Auth-Token (with # fallback to X-Storage-Token) # TODO(darrell): maybe care about HTTP_X_SERVICE_TOKEN if we need to? if not s3: # TDOO(darrell): remove this when adding support for Swift API # requests return HTTPBadRequest(body='Only S3 API requests are supported ' 'at this time.')(env, start_response) if s3 and self.s3_ok(env, s3): return self.app(env, start_response) # Unauthorized or missing token return HTTPUnauthorized(headers={ 'Www-Authenticate': 'Cloud-connector realm="unknown"'})( env, start_response)
def _verify_access(self, cont, obj): """ Verifies access to the specified object in swift :param cont: swift container name :param obj: swift object name :raise HTTPNotFound: if the object doesn't exists in swift :return response: Object response """ if obj: path = os.path.join('/', self.api_version, self.account, cont, obj) else: path = os.path.join('/', self.api_version, self.account, cont) self.logger.debug('Verifying access to %s' % path) new_env = dict(self.req.environ) if 'HTTP_TRANSFER_ENCODING' in new_env.keys(): del new_env['HTTP_TRANSFER_ENCODING'] auth_token = self.req.headers.get('X-Auth-Token') sub_req = make_subrequest(new_env, 'HEAD', path, headers={'X-Auth-Token': auth_token}, swift_source='function_middleware') resp = sub_req.get_response(self.app) if not resp.is_success: if resp.status_int == 401: raise HTTPUnauthorized('Unauthorized to access to this ' 'resource: ' + path + '\n') else: raise HTTPNotFound('There was an error: "' + path + ' doesn\'t exists in Swift.\n')
def run(self, req_resp, storlet_list, data_iter): on_other_server = {} # Execute multiple Storlets, PIPELINE, if any. for key in sorted(storlet_list): storlet, params, server = self._get_storlet_data(storlet_list[key]) if server == self.server: self._setup_gateway() self.logger.info('Vertigo - Go to execute ' + storlet + ' storlet with parameters "' + params + '"') if not self._verify_access_to_storlet(storlet): return HTTPUnauthorized('Vertigo - Storlet ' + storlet + ': No permission') self._setup_gateway() data_iter = self._call_gateway(req_resp, params, data_iter) else: storlet_execution = { 'storlet': storlet, 'params': params, 'server': server } launch_key = len(on_other_server.keys()) on_other_server[launch_key] = storlet_execution if on_other_server: req_resp.headers['Storlet-List'] = json.dumps(on_other_server) if isinstance(req_resp, Request): req_resp.environ['wsgi.input'] = data_iter else: req_resp.app_iter = data_iter return req_resp
def authorize(self, req): self.app.logger.info('StackSync API: authorize: path info: %s', req.path) if 'swift.authorize' in req.environ: resp = req.environ['swift.authorize'](req) del req.environ['swift.authorize'] return resp return HTTPUnauthorized()
def get_mc_deletion_data(self): header = [i for i in self.available_deletion_headers if i in self.request.headers.keys()] if len(header) > 1: raise HTTPUnauthorized('Vertigo - The system can only delete 1' ' microcontroller each time.\n') mc = self.request.headers[header[0]] return header[0].rsplit('-', 2)[1].lower(), mc
def denied_response(self, req): """ Returns a standard WSGI response callable with the status of 403 or 401 depending on whether the REMOTE_USER is set or not. """ if req.remote_user: return HTTPForbidden(request=req) else: return HTTPUnauthorized(request=req)
def test_auth_fail(self): app = container_quotas.ContainerQuotaMiddleware(FakeApp(), {}) cache = FakeCache({'object_count': 1, 'meta': {'quota-count': '1'}, 'write_acl': None}) req = Request.blank( '/v1/a/c/o', environ={'REQUEST_METHOD': 'PUT', 'swift.cache': cache, 'CONTENT_LENGTH': '100', 'swift.authorize': lambda *args: HTTPUnauthorized()}) res = req.get_response(app) self.assertEquals(res.status_int, 401)
def isTokenValid(req): try: version, account, container, obj = split_path( req.path, 1, 4, True) except ValueError: return HTTPNotFound(request=req) if 'prefix' in creds and creds['prefix']: if not obj.startswith(creds['prefix']): return HTTPUnauthorized(request=req) if req.method not in ('GET', 'HEAD'): if 'ro' in creds and creds['ro']: return HTTPUnauthorized(request=req) if account != creds['account']: return HTTPUnauthorized(request=req) else: uc = urllib.parse.unquote(container) if uc != creds['container'] and uc != creds[ 'container'] + '_segments': return HTTPUnauthorized(request=req) try: payload = {} if 'jti' in creds: payload['jti'] = creds['jti'] r = requests.get(creds['herodote_url'] + '/auth/swift/' + creds['owner'] + '/' + creds['container'] + '/' + creds['user'], params=payload) if r.status_code != 200: return HTTPUnauthorized(request=req) return None except Exception: return HTTPUnauthorized(request=req)
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. 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) token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) if token and token.startswith(self.reseller_prefix): 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, 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: # Invalid token (may be expired) if self.auth_method == "active": return HTTPSeeOther(location=self.ext_authentication_url)( env, start_response) elif self.auth_method == "passive": self.logger.increment('unauthorized') return HTTPUnauthorized()(env, start_response) else: # 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') # Not my token, not my account, I can't authorize this request, # deny all is a good idea if not already set... if 'swift.authorize' not in env: env['swift.authorize'] = self.denied_response return self.app(env, start_response)
def _get_function_unset_data(self): header = [ i for i in self.available_unset_headers if i in self.req.headers.keys() ] if len(header) > 1: raise HTTPUnauthorized('The system can only unset 1 ' 'function at a time.\n') trigger = header[0].lower().split('-', 2)[2].rsplit('-', 1)[0] function = self.req.headers[header[0]] return trigger, function
def denied_response(self, req): """ Returns a standard WSGI response callable with the status of 403 or 401 depending on whether the REMOTE_USER is set or not. """ if req.remote_user: self.logger.increment('forbidden') return HTTPForbidden(request=req) else: if self.auth_method == "active": return HTTPSeeOther(location=self.ext_authentication_url) elif self.auth_method == "passive": self.logger.increment('unauthorized') return HTTPUnauthorized(request=req)
def _invalid(self, env, start_response): """ Performs the necessary steps to indicate a WSGI 401 Unauthorized response to the request. :param env: The WSGI environment for the request. :param start_response: The WSGI start_response hook. :returns: 401 response as per WSGI. """ if env['REQUEST_METHOD'] == 'HEAD': body = None else: body = '401 Unauthorized: Temp URL invalid\n' return HTTPUnauthorized(body=body)(env, start_response)
def __call__(self, env, start_response): s3 = env.get('s3api.auth_details') if not s3: # TODO: remove this when adding support for Swift API # requests return HTTPBadRequest(body='Only S3 API requests are supported ' 'at this time.')(env, start_response) if s3 and self.s3_ok(env, s3): return self.app(env, start_response) # Unauthorized or missing token return HTTPUnauthorized( headers={'Www-Authenticate': 'Cloud-connector realm="unknown"'})( env, start_response)
def PUT(self): """ PUT handler on Proxy """ if self.is_function_object_put: if not self._check_mandatory_metadata(): msg = ('Mandatory function metadata not provided: ' + str(self.mandatory_function_metadata) + '\n') raise HTTPUnauthorized(msg) elif self.function_data: self.logger.info('There are functions to execute: ' + str(self.function_data)) return self._handle_put_trough_compute_node() return self.req.get_response(self.app)
def create_error_response(error, message): if error == 400: response = HTTPBadRequest(body=message) elif error == 401: response = HTTPUnauthorized(body=message) elif error == 403: response = HTTPForbidden(body=message) elif error == 404: response = HTTPNotFound(body=message) elif error == 405: response = HTTPMethodNotAllowed(body=message) else: response = HTTPServerError(body=message) return response
def _get_function_set_data(self): params = dict() header = [ i for i in self.available_set_headers if i in self.req.headers.keys() ] if len(header) > 1: raise HTTPUnauthorized('The system can only set 1 ' 'function at a time.\n') trigger = header[0].lower().split('-', 2)[2] function = self.req.headers[header[0]] if self.req.body: params = self.req.body return trigger, function, params
def __call__(self, env, start_response): if self.status == 200: start_response(Response().status, [('Content-Type', 'text/xml')]) json_pattern = ['"name":%s', '"count":%s', '"bytes":%s'] json_pattern = '{' + ','.join(json_pattern) + '}' json_out = [] for b in self.buckets: name = simplejson.dumps(b[0]) json_out.append(json_pattern % (name, b[1], b[2])) account_list = '[' + ','.join(json_out) + ']' return account_list elif self.status == 401: start_response(HTTPUnauthorized().status, []) elif self.status == 403: start_response(HTTPForbidden().status, []) else: start_response(HTTPBadRequest().status, []) return []
def __call__(self, req): if not self.allow_full_urls: sync_to = req.headers.get('x-container-sync-to') if sync_to and not sync_to.startswith('//'): raise HTTPBadRequest( body='Full URLs are not allowed for X-Container-Sync-To ' 'values. Only realm values of the format ' '//realm/cluster/account/container are allowed.\n', request=req) auth = req.headers.get('x-container-sync-auth') if auth: valid = False auth = auth.split() if len(auth) != 3: req.environ.setdefault('swift.log_info', []).append( 'cs:not-3-args') else: realm, nonce, sig = auth realm_key = self.realms_conf.key(realm) realm_key2 = self.realms_conf.key2(realm) if not realm_key: req.environ.setdefault('swift.log_info', []).append( 'cs:no-local-realm-key') else: info = get_container_info( req.environ, self.app, swift_source='CS') user_key = info.get('sync_key') if not user_key: req.environ.setdefault('swift.log_info', []).append( 'cs:no-local-user-key') else: # x-timestamp headers get shunted by gatekeeper if 'x-backend-inbound-x-timestamp' in req.headers: req.headers['x-timestamp'] = req.headers.pop( 'x-backend-inbound-x-timestamp') expected = self.realms_conf.get_sig( req.method, req.path, req.headers.get('x-timestamp', '0'), nonce, realm_key, user_key) expected2 = self.realms_conf.get_sig( req.method, req.path, req.headers.get('x-timestamp', '0'), nonce, realm_key2, user_key) if realm_key2 else expected if not streq_const_time(sig, expected) and \ not streq_const_time(sig, expected2): req.environ.setdefault( 'swift.log_info', []).append('cs:invalid-sig') else: req.environ.setdefault( 'swift.log_info', []).append('cs:valid') valid = True if not valid: exc = HTTPUnauthorized( body='X-Container-Sync-Auth header not valid; ' 'contact cluster operator for support.', headers={'content-type': 'text/plain'}, request=req) exc.headers['www-authenticate'] = ' '.join([ 'SwiftContainerSync', exc.www_authenticate().split(None, 1)[1]]) raise exc else: req.environ['swift.authorize_override'] = True # An SLO manifest will already be in the internal manifest # syntax and might be synced before its segments, so stop SLO # middleware from performing the usual manifest validation. req.environ['swift.slo_override'] = True if req.path == '/info': # Ensure /info requests get the freshest results self.register_info() return self.app
def __call__(self, req): if not self.allow_full_urls: sync_to = req.headers.get('x-container-sync-to') if sync_to and not sync_to.startswith('//'): raise HTTPBadRequest( body='Full URLs are not allowed for X-Container-Sync-To ' 'values. Only realm values of the format ' '//realm/cluster/account/container are allowed.\n', request=req) auth = req.headers.get('x-container-sync-auth') if auth: valid = False auth = auth.split() if len(auth) != 3: req.environ.setdefault('swift.log_info', []).append( 'cs:not-3-args') else: realm, nonce, sig = auth realm_key = self.realms_conf.key(realm) realm_key2 = self.realms_conf.key2(realm) if not realm_key: req.environ.setdefault('swift.log_info', []).append( 'cs:no-local-realm-key') else: info = get_container_info( req.environ, self.app, swift_source='CS') user_key = info.get('sync_key') if not user_key: req.environ.setdefault('swift.log_info', []).append( 'cs:no-local-user-key') else: expected = self.realms_conf.get_sig( req.method, req.path, req.headers.get('x-timestamp', '0'), nonce, realm_key, user_key) expected2 = self.realms_conf.get_sig( req.method, req.path, req.headers.get('x-timestamp', '0'), nonce, realm_key2, user_key) if realm_key2 else expected if not streq_const_time(sig, expected) and \ not streq_const_time(sig, expected2): req.environ.setdefault( 'swift.log_info', []).append('cs:invalid-sig') else: req.environ.setdefault( 'swift.log_info', []).append('cs:valid') valid = True if not valid: exc = HTTPUnauthorized( body='X-Container-Sync-Auth header not valid; ' 'contact cluster operator for support.', headers={'content-type': 'text/plain'}, request=req) exc.headers['www-authenticate'] = ' '.join([ 'SwiftContainerSync', exc.www_authenticate().split(None, 1)[1]]) raise exc else: req.environ['swift.authorize_override'] = True if req.path == '/info': # Ensure /info requests get the freshest results dct = {} for realm in self.realms_conf.realms(): clusters = self.realms_conf.clusters(realm) if clusters: dct[realm] = {'clusters': dict((c, {}) for c in clusters)} register_swift_info('container_sync', realms=dct) return self.app