From: Radoslaw Zarzynski Date: Mon, 7 Nov 2016 11:09:45 +0000 (+0100) Subject: rgw: Keystone implementation can support multiple instances now. X-Git-Tag: v12.0.2~305^2~41 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=9ddb0494c522b8b658ffd1d812258dd42560037b;p=ceph.git rgw: Keystone implementation can support multiple instances now. Signed-off-by: Radoslaw Zarzynski --- diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index 060b10c12358..8b189d13aff2 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -297,7 +297,8 @@ RGWKeystoneAuthEngine::decode_pki_token(const std::string& token) const } rgw::keystone::TokenEnvelope token_body; - ret = token_body.parse(cct, token, token_body_bl); + ret = token_body.parse(cct, token, token_body_bl, + rgw::keystone::CephCtxConfig::get_instance().get_api_version()); if (ret < 0) { throw ret; } @@ -314,12 +315,13 @@ RGWKeystoneAuthEngine::get_from_keystone(const std::string& token) const bufferlist token_body_bl; RGWValidateKeystoneToken validate(cct, &token_body_bl); - std::string url; - if (rgw::keystone::Service::get_keystone_url(cct, url) < 0) { + std::string url + = rgw::keystone::CephCtxConfig::get_instance().get_endpoint_url(); + if (url.empty()) { throw -EINVAL; } - const auto keystone_version = rgw::keystone::Service::get_api_version(); + const auto keystone_version = rgw::keystone::CephCtxConfig::get_instance().get_api_version(); if (keystone_version == rgw::keystone::ApiVersion::VER_2) { url.append("v2.0/tokens/" + token); } else if (keystone_version == rgw::keystone::ApiVersion::VER_3) { @@ -328,7 +330,11 @@ RGWKeystoneAuthEngine::get_from_keystone(const std::string& token) const } std::string admin_token; - if (rgw::keystone::Service::get_keystone_admin_token(cct, admin_token) < 0) { + if (rgw::keystone::Service::get_admin_token( + cct, + rgw::keystone::TokenCache::get_instance(), + rgw::keystone::CephCtxConfig::get_instance(), + admin_token) < 0) { throw -EINVAL; } @@ -359,7 +365,7 @@ RGWKeystoneAuthEngine::get_from_keystone(const std::string& token) const } rgw::keystone::TokenEnvelope token_body; - ret = token_body.parse(cct, token, token_body_bl); + ret = token_body.parse(cct, token, token_body_bl, keystone_version); if (ret < 0) { throw ret; } @@ -465,7 +471,7 @@ RGWAuthApplier::aplptr_t RGWKeystoneAuthEngine::authenticate() const ldout(cct, 20) << "token_id=" << token_id << dendl; /* Check cache first. */ - if (rgw::keystone::TokenCache::get_instance().find(token_id, t)) { + if (rgw::keystone::TokenCache::get_instance().find(token_id, t)) { ldout(cct, 20) << "cached token.project.id=" << t.get_project_id() << dendl; return apl_factory->create_apl_remote(cct, @@ -500,7 +506,7 @@ RGWAuthApplier::aplptr_t RGWKeystoneAuthEngine::authenticate() const ldout(cct, 0) << "validated token: " << t.get_project_name() << ":" << t.get_user_name() << " expires: " << t.get_expires() << dendl; - rgw::keystone::TokenCache::get_instance().add(token_id, t); + rgw::keystone::TokenCache::get_instance().add(token_id, t); return apl_factory->create_apl_remote(cct, get_acl_strategy(t), get_creds_info(t, roles.admin)); diff --git a/src/rgw/rgw_json_enc.cc b/src/rgw/rgw_json_enc.cc index b16452b78305..35920721200e 100644 --- a/src/rgw/rgw_json_enc.cc +++ b/src/rgw/rgw_json_enc.cc @@ -1204,59 +1204,34 @@ void rgw::keystone::TokenEnvelope::User::decode_json(JSONObj *obj) JSONDecoder::decode_json("roles", roles_v2, obj); } -void rgw::keystone::TokenEnvelope::decode_json(JSONObj *root_obj) +void rgw::keystone::TokenEnvelope::decode_v3(JSONObj* const root_obj) { + std::string expires_iso8601; + JSONDecoder::decode_json("user", user, root_obj, true); + JSONDecoder::decode_json("expires_at", expires_iso8601, root_obj, true); + JSONDecoder::decode_json("roles", roles, root_obj, true); + JSONDecoder::decode_json("project", project, root_obj, true); - const auto version = rgw::keystone::Service::get_api_version(); - - if (version == rgw::keystone::ApiVersion::VER_3) { - string expires_iso8601; - if (JSONDecoder::decode_json("expires_at", expires_iso8601, root_obj)) { - /* VER_3 */ - /* Presence of "expires_at" suggests we are dealing with OpenStack - * Identity API v3 (aka Keystone API v3) token. */ - struct tm t; - - if (parse_iso8601(expires_iso8601.c_str(), &t)) { - token.expires = timegm(&t); - } else { - token.expires = 0; - throw JSONDecoder::err("Failed to parse ISO8601 expiration date" - "from Keystone response."); - } - JSONDecoder::decode_json("roles", roles, root_obj, true); - JSONDecoder::decode_json("project", project, root_obj, true); - } else { - /* fallback: VER_2 */ - JSONDecoder::decode_json("token", token, root_obj, true); - roles = user.roles_v2; - project = token.tenant_v2; - } - } else if (version == rgw::keystone::ApiVersion::VER_2) { - if (JSONDecoder::decode_json("token", token, root_obj)) { - /* VER_2 */ - roles = user.roles_v2; - project = token.tenant_v2; - } else { - /* fallback: VER_3 */ - string expires_iso8601; - JSONDecoder::decode_json("expires_at", expires_iso8601, root_obj, true); - - struct tm t; - if (parse_iso8601(expires_iso8601.c_str(), &t)) { - token.expires = timegm(&t); - } else { - token.expires = 0; - throw JSONDecoder::err("Failed to parse ISO8601 expiration date" - "from Keystone response."); - } - JSONDecoder::decode_json("roles", roles, root_obj, true); - JSONDecoder::decode_json("project", project, root_obj, true); - } + struct tm t; + if (parse_iso8601(expires_iso8601.c_str(), &t)) { + token.expires = timegm(&t); + } else { + token.expires = 0; + throw JSONDecoder::err("Failed to parse ISO8601 expiration date" + "from Keystone response."); } } +void rgw::keystone::TokenEnvelope::decode_v2(JSONObj* const root_obj) +{ + JSONDecoder::decode_json("user", user, root_obj, true); + JSONDecoder::decode_json("token", token, root_obj, true); + + roles = user.roles_v2; + project = token.tenant_v2; +} + void rgw_slo_entry::decode_json(JSONObj *obj) { JSONDecoder::decode_json("path", path, obj); @@ -1344,15 +1319,24 @@ void rgw_sync_error_info::dump(Formatter *f) const { encode_json("message", message, f); } +/* This utility function shouldn't conflict with the overload of std::to_string + * provided by string_ref since Boost 1.54 as it's defined outside of the std + * namespace. I hope we'll remove it soon - just after merging the Matt's PR + * for bundled Boost. It would allow us to forget that CentOS 7 has Boost 1.53. */ +static inline std::string to_string(const boost::string_ref& s) +{ + return std::string(s.data(), s.length()); +} + void rgw::keystone::AdminTokenRequestVer2::dump(Formatter* const f) const { f->open_object_section("token_request"); f->open_object_section("auth"); f->open_object_section("passwordCredentials"); - encode_json("username", cct->_conf->rgw_keystone_admin_user, f); - encode_json("password", cct->_conf->rgw_keystone_admin_password, f); + encode_json("username", to_string(conf.get_admin_user()), f); + encode_json("password", to_string(conf.get_admin_password()), f); f->close_section(); - encode_json("tenantName", cct->_conf->rgw_keystone_admin_tenant, f); + encode_json("tenantName", to_string(conf.get_admin_tenant()), f); f->close_section(); f->close_section(); } @@ -1368,22 +1352,22 @@ void rgw::keystone::AdminTokenRequestVer3::dump(Formatter* const f) const f->open_object_section("password"); f->open_object_section("user"); f->open_object_section("domain"); - encode_json("name", cct->_conf->rgw_keystone_admin_domain, f); + encode_json("name", to_string(conf.get_admin_domain()), f); f->close_section(); - encode_json("name", cct->_conf->rgw_keystone_admin_user, f); - encode_json("password", cct->_conf->rgw_keystone_admin_password, f); + encode_json("name", to_string(conf.get_admin_user()), f); + encode_json("password", to_string(conf.get_admin_password()), f); f->close_section(); f->close_section(); f->close_section(); f->open_object_section("scope"); f->open_object_section("project"); - if (!cct->_conf->rgw_keystone_admin_project.empty()) { - encode_json("name", cct->_conf->rgw_keystone_admin_project, f); + if (! conf.get_admin_project().empty()) { + encode_json("name", to_string(conf.get_admin_project()), f); } else { - encode_json("name", cct->_conf->rgw_keystone_admin_tenant, f); + encode_json("name", to_string(conf.get_admin_tenant()), f); } f->open_object_section("domain"); - encode_json("name", cct->_conf->rgw_keystone_admin_domain, f); + encode_json("name", to_string(conf.get_admin_domain()), f); f->close_section(); f->close_section(); f->close_section(); diff --git a/src/rgw/rgw_keystone.cc b/src/rgw/rgw_keystone.cc index 97116f5cd88b..11583572d229 100644 --- a/src/rgw/rgw_keystone.cc +++ b/src/rgw/rgw_keystone.cc @@ -4,6 +4,8 @@ #include #include +#include + #include "common/errno.h" #include "common/ceph_json.h" #include "include/types.h" @@ -141,68 +143,82 @@ bool rgw_decode_pki_token(CephContext * const cct, namespace rgw { namespace keystone { -ApiVersion Service::get_api_version() +ApiVersion CephCtxConfig::get_api_version() const noexcept { - const int keystone_version = g_ceph_context->_conf->rgw_keystone_api_version; - - if (keystone_version == 3) { + switch (g_ceph_context->_conf->rgw_keystone_api_version) { + case 3: return ApiVersion::VER_3; - } else if (keystone_version == 2) { + case 2: return ApiVersion::VER_2; - } else { - dout(0) << "ERROR: wrong Keystone API version: " << keystone_version + default: + dout(0) << "ERROR: wrong Keystone API version: " + << g_ceph_context->_conf->rgw_keystone_api_version << "; falling back to v2" << dendl; return ApiVersion::VER_2; } } -int Service::get_keystone_url(CephContext* const cct, - std::string& url) +std::string CephCtxConfig::get_endpoint_url() const noexcept { - url = cct->_conf->rgw_keystone_url; - if (url.empty()) { - ldout(cct, 0) << "ERROR: keystone url is not configured" << dendl; - return -EINVAL; - } + static const std::string url = g_ceph_context->_conf->rgw_keystone_url; - if (url[url.size() - 1] != '/') { - url.append("/"); + if (url.empty() || boost::algorithm::ends_with(url, "/")) { + return url; + } else { + static const std::string url_normalised = url + '/'; + return url_normalised; } - - return 0; } -int Service::get_keystone_admin_token(CephContext* const cct, - std::string& token) +int Service::get_admin_token(CephContext* const cct, + TokenCache& token_cache, + const Config& config, + std::string& token) { - std::string token_url; - - if (get_keystone_url(cct, token_url) < 0) { - return -EINVAL; - } - - if (!cct->_conf->rgw_keystone_admin_token.empty()) { - token = cct->_conf->rgw_keystone_admin_token; + /* Let's check whether someone uses the deprecated "admin token" feauture + * based on a shared secret from keystone.conf file. */ + const auto& admin_token = config.get_admin_token(); + if (! admin_token.empty()) { + token = std::string(admin_token.data(), admin_token.length()); return 0; } TokenEnvelope t; - /* Try cache first. */ - if (TokenCache::get_instance().find_admin(t)) { + /* Try cache first before calling Keystone for a new admin token. */ + if (token_cache.find_admin(t)) { ldout(cct, 20) << "found cached admin token" << dendl; token = t.token.id; return 0; } + /* Call Keystone now. */ + const auto ret = issue_admin_token_request(cct, config, t); + if (! ret) { + token_cache.add_admin(t); + token = t.token.id; + } + + return ret; +} + +int Service::issue_admin_token_request(CephContext* const cct, + const Config& config, + TokenEnvelope& t) +{ + std::string token_url = config.get_endpoint_url(); + if (token_url.empty()) { + return -EINVAL; + } + bufferlist token_bl; RGWGetKeystoneAdminToken token_req(cct, &token_bl); token_req.append_header("Content-Type", "application/json"); JSONFormatter jf; - const auto keystone_version = Service::get_api_version(); + const auto keystone_version = config.get_api_version(); if (keystone_version == ApiVersion::VER_2) { - AdminTokenRequestVer2 req_serializer(cct); + AdminTokenRequestVer2 req_serializer(config); req_serializer.dump(&jf); std::stringstream ss; @@ -212,7 +228,7 @@ int Service::get_keystone_admin_token(CephContext* const cct, token_url.append("v2.0/tokens"); } else if (keystone_version == ApiVersion::VER_3) { - AdminTokenRequestVer3 req_serializer(cct); + AdminTokenRequestVer3 req_serializer(config); req_serializer.dump(&jf); std::stringstream ss; @@ -235,12 +251,11 @@ int Service::get_keystone_admin_token(CephContext* const cct, return -EACCES; } - if (t.parse(cct, token_req.get_subject_token(), token_bl) != 0) { + if (t.parse(cct, token_req.get_subject_token(), token_bl, + keystone_version) != 0) { return -EINVAL; } - TokenCache::get_instance().add_admin(t); - token = t.token.id; return 0; } @@ -258,33 +273,47 @@ bool TokenEnvelope::has_role(const std::string& r) const int TokenEnvelope::parse(CephContext* const cct, const std::string& token_str, - ceph::bufferlist& bl) + ceph::bufferlist& bl, + const ApiVersion version) { JSONParser parser; - if (!parser.parse(bl.c_str(), bl.length())) { + if (! parser.parse(bl.c_str(), bl.length())) { ldout(cct, 0) << "Keystone token parse error: malformed json" << dendl; return -EINVAL; } - try { - const auto version = rgw::keystone::Service::get_api_version(); + JSONObjIter token_iter = parser.find_first("token"); + JSONObjIter access_iter = parser.find_first("access"); + try { if (version == rgw::keystone::ApiVersion::VER_2) { - if (!JSONDecoder::decode_json("access", *this, &parser)) { - /* TokenEnvelope structure doesn't follow Identity API v2, so the token - * must be in v3. Otherwise we can assume it's wrongly formatted. */ - JSONDecoder::decode_json("token", *this, &parser, true); + if (! access_iter.end()) { + decode_v2(*access_iter); + } else if (! token_iter.end()) { + /* TokenEnvelope structure doesn't follow Identity API v2, so let's + * fallback to v3. Otherwise we can assume it's wrongly formatted. + * The whole mechanism is a workaround for s3_token middleware that + * speaks in v2 disregarding the promise to go with v3. */ + decode_v3(*token_iter); + + /* Identity v3 conveys the token inforamtion not as a part of JSON but + * in the X-Subject-Token HTTP header we're getting from caller. */ token.id = token_str; + } else { + return -EINVAL; } } else if (version == rgw::keystone::ApiVersion::VER_3) { - if (!JSONDecoder::decode_json("token", *this, &parser)) { - /* If the token cannot be parsed according to V3, try V2. */ - JSONDecoder::decode_json("access", *this, &parser, true); - } else { + if (! token_iter.end()) { + decode_v3(*token_iter); /* v3 suceeded. We have to fill token.id from external input as it * isn't a part of the JSON response anymore. It has been moved * to X-Subject-Token HTTP header instead. */ token.id = token_str; + } else if (! access_iter.end()) { + /* If the token cannot be parsed according to V3, try V2. */ + decode_v2(*access_iter); + } else { + return -EINVAL; } } else { return -ENOTSUP; @@ -297,13 +326,6 @@ int TokenEnvelope::parse(CephContext* const cct, return 0; } -TokenCache& TokenCache::get_instance() -{ - /* In C++11 this is thread safe. */ - static TokenCache instance; - return instance; -} - bool TokenCache::find(const std::string& token_id, rgw::keystone::TokenEnvelope& token) { @@ -406,15 +428,18 @@ int TokenCache::RevokeThread::check_revoked() bufferlist bl; RGWGetRevokedTokens req(cct, &bl); - if (rgw::keystone::Service::get_keystone_admin_token(cct, token) < 0) { + if (rgw::keystone::Service::get_admin_token(cct, *cache, config, token) < 0) { return -EINVAL; } - if (rgw::keystone::Service::get_keystone_url(cct, url) < 0) { + + url = config.get_endpoint_url(); + if (url.empty()) { return -EINVAL; } + req.append_header("X-Auth-Token", token); - const auto keystone_version = rgw::keystone::Service::get_api_version(); + const auto keystone_version = config.get_api_version(); if (keystone_version == rgw::keystone::ApiVersion::VER_2) { url.append("v2.0/tokens/revoked"); } else if (keystone_version == rgw::keystone::ApiVersion::VER_3) { diff --git a/src/rgw/rgw_keystone.h b/src/rgw/rgw_keystone.h index c59ebbd0e608..6a99fa74a57e 100644 --- a/src/rgw/rgw_keystone.h +++ b/src/rgw/rgw_keystone.h @@ -4,6 +4,10 @@ #ifndef CEPH_RGW_KEYSTONE_H #define CEPH_RGW_KEYSTONE_H +#include + +#include + #include "rgw_common.h" #include "rgw_http_client.h" #include "common/Cond.h" @@ -35,6 +39,67 @@ enum class ApiVersion { VER_3 }; + +class Config { +protected: + Config() = default; + virtual ~Config() = default; + +public: + virtual std::string get_endpoint_url() const noexcept = 0; + virtual ApiVersion get_api_version() const noexcept = 0; + + virtual boost::string_ref get_admin_token() const noexcept = 0; + virtual boost::string_ref get_admin_user() const noexcept = 0; + virtual boost::string_ref get_admin_password() const noexcept = 0; + virtual boost::string_ref get_admin_tenant() const noexcept = 0; + virtual boost::string_ref get_admin_project() const noexcept = 0; + virtual boost::string_ref get_admin_domain() const noexcept = 0; +}; + +class CephCtxConfig : public Config { +protected: + CephCtxConfig() = default; + virtual ~CephCtxConfig() = default; + +public: + static CephCtxConfig& get_instance() { + static CephCtxConfig instance; + return instance; + } + + std::string get_endpoint_url() const noexcept override; + ApiVersion get_api_version() const noexcept override; + + boost::string_ref get_admin_token() const noexcept override { + return g_ceph_context->_conf->rgw_keystone_admin_token; + } + + boost::string_ref get_admin_user() const noexcept override { + return g_ceph_context->_conf->rgw_keystone_admin_user; + } + + boost::string_ref get_admin_password() const noexcept override { + return g_ceph_context->_conf->rgw_keystone_admin_password; + } + + boost::string_ref get_admin_tenant() const noexcept override { + return g_ceph_context->_conf->rgw_keystone_admin_tenant; + } + + boost::string_ref get_admin_project() const noexcept override { + return g_ceph_context->_conf->rgw_keystone_admin_project; + } + + boost::string_ref get_admin_domain() const noexcept override { + return g_ceph_context->_conf->rgw_keystone_admin_domain; + } +}; + + +class TokenEnvelope; +class TokenCache; + class Service { public: class RGWKeystoneHTTPTransceiver : public RGWHTTPTransceiver { @@ -60,12 +125,13 @@ public: typedef RGWKeystoneHTTPTransceiver RGWGetKeystoneAdminToken; typedef RGWKeystoneHTTPTransceiver RGWGetRevokedTokens; - static ApiVersion get_api_version(); - - static int get_keystone_url(CephContext * const cct, - std::string& url); - static int get_keystone_admin_token(CephContext * const cct, - std::string& token); + static int get_admin_token(CephContext* const cct, + TokenCache& token_cache, + const Config& config, + std::string& token); + static int issue_admin_token_request(CephContext* const cct, + const Config& config, + TokenEnvelope& token); }; @@ -115,9 +181,13 @@ public: User user; list roles; + void decode_v3(JSONObj* obj); + void decode_v2(JSONObj* obj); + public: - // FIXME: default ctor needs to be eradicated here + /* We really need the default ctor because of the internals of TokenCache. */ TokenEnvelope() = default; + time_t get_expires() const { return token.expires; } const std::string& get_domain_id() const {return project.domain.id;}; const std::string& get_domain_name() const {return project.domain.name;}; @@ -130,10 +200,10 @@ public: uint64_t now = ceph_clock_now().sec(); return (now >= (uint64_t)get_expires()); } - int parse(CephContext *cct, - const string& token_str, - bufferlist& bl /* in */); - void decode_json(JSONObj *access_obj); + int parse(CephContext* cct, + const std::string& token_str, + ceph::buffer::list& bl /* in */, + ApiVersion version); }; @@ -143,7 +213,6 @@ class TokenCache { list::iterator lru_iter; }; - const rgw::keystone::Config& config; atomic_t down_flag; class RevokeThread : public Thread { @@ -152,13 +221,17 @@ class TokenCache { CephContext * const cct; TokenCache* const cache; + const rgw::keystone::Config& config; + Mutex lock; Cond cond; RevokeThread(CephContext* const cct, - TokenCache* const cache) + TokenCache* const cache, + const rgw::keystone::Config& config) : cct(cct), cache(cache), + config(config), lock("rgw::keystone::TokenCache::RevokeThread") { } void *entry() override; @@ -176,8 +249,8 @@ class TokenCache { const size_t max; - TokenCache() - : revocator(g_ceph_context, this), + TokenCache(const rgw::keystone::Config& config) + : revocator(g_ceph_context, this, config), cct(g_ceph_context), lock("rgw::keystone::TokenCache"), max(cct->_conf->rgw_keystone_token_cache_size) { @@ -196,7 +269,10 @@ public: TokenCache(const TokenCache&) = delete; void operator=(const TokenCache&) = delete; - static TokenCache& get_instance(); + template + static TokenCache& get_instance() { + static_assert(std::is_base_of::value, + "ConfigT must be a subclass of rgw::keystone::Config"); /* In C++11 this is thread safe. */ static TokenCache instance(ConfigT::get_instance()); @@ -223,21 +299,21 @@ public: }; class AdminTokenRequestVer2 : public AdminTokenRequest { - CephContext* cct; + const Config& conf; public: - AdminTokenRequestVer2(CephContext* const cct) - : cct(cct) { + AdminTokenRequestVer2(const Config& conf) + : conf(conf) { } void dump(Formatter *f) const override; }; class AdminTokenRequestVer3 : public AdminTokenRequest { - CephContext* cct; + const Config& conf; public: - AdminTokenRequestVer3(CephContext* const cct) - : cct(cct) { + AdminTokenRequestVer3(const Config& conf) + : conf(conf) { } void dump(Formatter *f) const override; }; diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index edb147c4ba0d..c6918cc8d867 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3302,7 +3302,8 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_s3token( keystone_url.append("/"); } - if (rgw::keystone::Service::get_api_version() == rgw::keystone::ApiVersion::VER_3) { + if (rgw::keystone::CephCtxConfig::get_instance().get_api_version() + == rgw::keystone::ApiVersion::VER_3) { keystone_url.append("v3/s3tokens"); } else { keystone_url.append("v2.0/s3tokens"); @@ -3310,7 +3311,12 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_s3token( /* get authentication token for Keystone. */ string admin_token_id; - int r = rgw::keystone::Service::get_keystone_admin_token(cct, admin_token_id); + int r = rgw::keystone::Service::get_admin_token( + cct, + rgw::keystone::TokenCache::get_instance(), + rgw::keystone::CephCtxConfig::get_instance(), + admin_token_id); + if (r < 0) { ldout(cct, 2) << "s3 keystone: cannot get token for keystone access" << dendl; return r; @@ -3358,7 +3364,8 @@ int RGW_Auth_S3_Keystone_ValidateToken::validate_s3token( } /* now parse response */ - if (response.parse(cct, string(), rx_buffer) < 0) { + if (response.parse(cct, string(), rx_buffer, + rgw::keystone::CephCtxConfig::get_instance().get_api_version()) < 0) { dout(2) << "s3 keystone: token parsing failed" << dendl; return -EPERM; }