}
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;
}
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) {
}
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>(),
+ rgw::keystone::CephCtxConfig::get_instance(),
+ admin_token) < 0) {
throw -EINVAL;
}
}
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;
}
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<rgw::keystone::CephCtxConfig>().find(token_id, t)) {
ldout(cct, 20) << "cached token.project.id=" << t.get_project_id()
<< dendl;
return apl_factory->create_apl_remote(cct,
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<rgw::keystone::CephCtxConfig>().add(token_id, t);
return apl_factory->create_apl_remote(cct,
get_acl_strategy(t),
get_creds_info(t, roles.admin));
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);
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();
}
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();
#include <errno.h>
#include <fnmatch.h>
+#include <boost/algorithm/string/predicate.hpp>
+
#include "common/errno.h"
#include "common/ceph_json.h"
#include "include/types.h"
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<rgw::keystone::LegacyConfig>().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;
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;
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;
}
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;
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)
{
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) {
#ifndef CEPH_RGW_KEYSTONE_H
#define CEPH_RGW_KEYSTONE_H
+#include <type_traits>
+
+#include <boost/utility/string_ref.hpp>
+
#include "rgw_common.h"
#include "rgw_http_client.h"
#include "common/Cond.h"
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 {
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);
};
User user;
list<Role> 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;};
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);
};
list<string>::iterator lru_iter;
};
- const rgw::keystone::Config& config;
atomic_t down_flag;
class RevokeThread : public Thread {
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;
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) {
TokenCache(const TokenCache&) = delete;
void operator=(const TokenCache&) = delete;
- static TokenCache& get_instance();
+ template<class ConfigT>
+ static TokenCache& get_instance() {
+ static_assert(std::is_base_of<rgw::keystone::Config, ConfigT>::value,
+ "ConfigT must be a subclass of rgw::keystone::Config");
/* In C++11 this is thread safe. */
static TokenCache instance(ConfigT::get_instance());
};
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;
};
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");
/* 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>(),
+ 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;
}
/* 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;
}