From fb72c54f0497539c3f807566da768eb42a2016dc Mon Sep 17 00:00:00 2001 From: Radoslaw Zarzynski Date: Wed, 6 Apr 2016 18:56:14 +0200 Subject: [PATCH] rgw: implement Swift-specific authentication engines. Signed-off-by: Radoslaw Zarzynski --- src/rgw/rgw_swift_auth.cc | 387 +++++++++++++++++++++++++++++++++++++- src/rgw/rgw_swift_auth.h | 94 ++++++++- 2 files changed, 478 insertions(+), 3 deletions(-) diff --git a/src/rgw/rgw_swift_auth.cc b/src/rgw/rgw_swift_auth.cc index 79ca0d10aa4b8..41c5f8193e62a 100644 --- a/src/rgw/rgw_swift_auth.cc +++ b/src/rgw/rgw_swift_auth.cc @@ -10,6 +10,9 @@ #include "auth/Crypto.h" #include "rgw_client_io.h" +#include "rgw_swift.h" +#include "rgw_http_client.h" +#include "include/str_list.h" #define dout_subsys ceph_subsys_rgw @@ -17,8 +20,288 @@ using namespace ceph::crypto; -static int build_token(string& swift_user, string& key, uint64_t nonce, - utime_t& expiration, bufferlist& bl) + +/* TempURL: applier */ +void RGWTempURLAuthApplier::modify_request_state(req_state * s) const /* in/out */ +{ + bool inline_exists = false; + string filename = s->info.args.get("filename"); + + s->info.args.get("inline", &inline_exists); + if (inline_exists) { + s->content_disp.override = "inline"; + } else if (!filename.empty()) { + string fenc; + url_encode(filename, fenc); + s->content_disp.override = "attachment; filename=\"" + fenc + "\""; + } else { + string fenc; + url_encode(s->object.name, fenc); + s->content_disp.fallback = "attachment; filename=\"" + fenc + "\""; + } + + ldout(s->cct, 20) << "finished applying changes to req_state for TempURL: " + << " content_disp override " << s->content_disp.override + << " content_disp fallback " << s->content_disp.fallback + << dendl; + +} + +/* TempURL: engine */ +bool RGWTempURLAuthEngine::is_applicable() const noexcept +{ + return s->info.args.exists("temp_url_sig") || + s->info.args.exists("temp_url_expires"); +} + +void RGWTempURLAuthEngine::get_owner_info(RGWUserInfo& owner_info) const +{ + /* We cannot use req_state::bucket_name because it isn't available + * now. It will be initialized in RGWHandler_REST_SWIFT::postauth_init(). */ + const string& bucket_name = s->init_state.url_bucket; + + /* TempURL requires that bucket and object names are specified. */ + if (bucket_name.empty() || s->object.empty()) { + throw -EPERM; + } + + /* TempURL case is completely different than the Keystone auth - you may + * get account name only through extraction from URL. In turn, knowledge + * about account is neccessary to obtain its bucket tenant. Without that, + * the access would be limited to accounts with empty tenant. */ + string bucket_tenant; + if (!s->account_name.empty()) { + RGWUserInfo uinfo; + bool found = false; + + const rgw_user uid(s->account_name); + if (uid.tenant.empty()) { + const rgw_user tenanted_uid(uid.id, uid.id); + + if (rgw_get_user_info_by_uid(store, tenanted_uid, uinfo) >= 0) { + /* Succeeded. */ + bucket_tenant = uinfo.user_id.tenant; + found = true; + } + } + + if (!found && rgw_get_user_info_by_uid(store, uid, uinfo) < 0) { + throw -EPERM; + } else { + bucket_tenant = uinfo.user_id.tenant; + } + } + + /* Need to get user info of bucket owner. */ + RGWBucketInfo bucket_info; + int ret = store->get_bucket_info(*static_cast(s->obj_ctx), + bucket_tenant, bucket_name, + bucket_info, nullptr); + if (ret < 0) { + throw ret; + } + + ldout(s->cct, 20) << "temp url user (bucket owner): " << bucket_info.owner + << dendl; + + if (rgw_get_user_info_by_uid(store, bucket_info.owner, owner_info) < 0) { + throw -EPERM; + } +} + +bool RGWTempURLAuthEngine::is_expired(const std::string& expires) const +{ + string err; + const utime_t now = ceph_clock_now(g_ceph_context); + const uint64_t expiration = (uint64_t)strict_strtoll(expires.c_str(), + 10, &err); + if (!err.empty()) { + dout(5) << "failed to parse temp_url_expires: " << err << dendl; + return true; + } + + if (expiration <= (uint64_t)now.sec()) { + dout(5) << "temp url expired: " << expiration << " <= " << now.sec() << dendl; + return true; + } + + return false; +} + +std::string extract_swift_subuser(const std::string& swift_user_name) { + size_t pos = swift_user_name.find(':'); + if (std::string::npos == pos) { + return swift_user_name; + } else { + return swift_user_name.substr(pos + 1); + } +} + +std::string RGWTempURLAuthEngine::generate_signature(const string& key, + const string& method, + const string& path, + const string& expires) const +{ + const string str = method + "\n" + expires + "\n" + path; + ldout(cct, 20) << "temp url signature (plain text): " << str << dendl; + + /* unsigned */ char dest[CEPH_CRYPTO_HMACSHA1_DIGESTSIZE]; + calc_hmac_sha1(key.c_str(), key.size(), + str.c_str(), str.size(), + dest); + + char dest_str[CEPH_CRYPTO_HMACSHA1_DIGESTSIZE * 2 + 1]; + buf_to_hex((const unsigned char *)dest, sizeof(dest), dest_str); + + return dest_str; +} + +RGWAuthApplier::aplptr_t RGWTempURLAuthEngine::authenticate() const +{ + const string temp_url_sig = s->info.args.get("temp_url_sig"); + const string temp_url_expires = s->info.args.get("temp_url_expires"); + if (temp_url_sig.empty() || temp_url_expires.empty()) { + return nullptr; + } + + RGWUserInfo owner_info; + try { + get_owner_info(owner_info); + } catch (...) { + ldout(cct, 5) << "cannot get user_info of account's owner" << dendl; + return nullptr; + } + + if (owner_info.temp_url_keys.empty()) { + ldout(cct, 5) << "user does not have temp url key set, aborting" << dendl; + return nullptr; + } + + if (is_expired(temp_url_expires)) { + ldout(cct, 5) << "temp url link expired" << dendl; + return nullptr; + } + + /* We need to verify two paths because of compliance with Swift, Tempest + * and old versions of RadosGW. The second item will have the prefix + * of Swift API entry point removed. */ + const size_t pos = g_conf->rgw_swift_url_prefix.find_last_not_of('/') + 1; + const std::vector allowed_paths = { + s->info.request_uri, + s->info.request_uri.substr(pos + 1) + }; + + /* Account owner calculates the signature also against a HTTP method. */ + std::vector allowed_methods; + if (strcmp("HEAD", s->info.method) == 0) { + /* HEAD requests are specially handled. */ + allowed_methods = { "HEAD", "GET", "PUT" }; + } else if (strlen(s->info.method) > 0) { + allowed_methods = { s->info.method }; + } + + /* Need to try each combination of keys, allowed path and methods. */ + for (const auto kv : owner_info.temp_url_keys) { + const int temp_url_key_num = kv.first; + const string& temp_url_key = kv.second; + + if (temp_url_key.empty()) { + continue; + } + + for (const auto path : allowed_paths) { + for (const auto method : allowed_methods) { + const std::string local_sig = generate_signature(temp_url_key, + method, path, + temp_url_expires); + + ldout(s->cct, 20) << "temp url signature [" << temp_url_key_num + << "] (calculated): " << local_sig + << dendl; + + if (local_sig != temp_url_sig) { + ldout(s->cct, 5) << "temp url signature mismatch: " << local_sig + << " != " << temp_url_sig << dendl; + } else { + return apl_factory->create_apl_turl(cct, owner_info); + } + } + } + } + + return nullptr; +} + + +/* External token */ +bool RGWExternalTokenAuthEngine::is_applicable() const noexcept +{ + if (false == RGWTokenBasedAuthEngine::is_applicable()) { + return false; + } + + return false == g_conf->rgw_swift_auth_url.empty(); +} + +RGWAuthApplier::aplptr_t RGWExternalTokenAuthEngine::authenticate() const +{ + string auth_url = g_conf->rgw_swift_auth_url; + if (auth_url[auth_url.length() - 1] != '/') { + auth_url.append("/"); + } + + auth_url.append("token"); + char url_buf[auth_url.size() + 1 + token.length() + 1]; + sprintf(url_buf, "%s/%s", auth_url.c_str(), token.c_str()); + + RGWHTTPHeadersCollector validator(cct, { "X-Auth-Groups", "X-Auth-Ttl" }); + + ldout(cct, 10) << "rgw_swift_validate_token url=" << url_buf << dendl; + + int ret = validator.process(url_buf); + if (ret < 0) { + throw ret; + } + + std::string swift_user; + try { + std::vector swift_groups; + get_str_vec(validator.get_header_value("X-Auth-Groups"), + ",", swift_groups); + + if (0 == swift_groups.size()) { + return nullptr; + } else { + swift_user = swift_groups[0]; + } + } catch (std::out_of_range) { + /* The X-Auth-Groups header isn't present in the response. */ + return nullptr; + } + + if (swift_user.empty()) { + return nullptr; + } + + ldout(cct, 10) << "swift user=" << swift_user << dendl; + + RGWUserInfo tmp_uinfo; + ret = rgw_get_user_info_by_swift(store, swift_user, tmp_uinfo); + if (ret < 0) { + ldout(cct, 0) << "NOTICE: couldn't map swift user" << dendl; + throw ret; + } + + return apl_factory->create_apl_local(cct, tmp_uinfo, + extract_swift_subuser(swift_user)); +} + + +static int build_token(const string& swift_user, + const string& key, + const uint64_t nonce, + const utime_t& expiration, + bufferlist& bl) { ::encode(swift_user, bl); ::encode(nonce, bl); @@ -138,6 +421,106 @@ int rgw_swift_verify_signed_token(CephContext *cct, RGWRados *store, return 0; } + +/* AUTH_rgwtk (signed token): engine */ +bool RGWSignedTokenAuthEngine::is_applicable() const noexcept +{ + if (false == RGWTokenBasedAuthEngine::is_applicable()) { + return false; + } + + return token.compare(0, 10, "AUTH_rgwtk") == 0; +} + +RGWAuthApplier::aplptr_t RGWSignedTokenAuthEngine::authenticate() const +{ + /* Effective token string is the part after the prefix. */ + const std::string etoken = token.substr(strlen("AUTH_rgwtk")); + const size_t etoken_len = etoken.length(); + + if (etoken_len & 1) { + ldout(cct, 0) << "NOTICE: failed to verify token: odd token length=" + << etoken_len << dendl; + throw -EINVAL; + } + + bufferptr p(etoken_len/2); + int ret = hex_to_buf(etoken.c_str(), p.c_str(), etoken_len); + if (ret < 0) { + throw ret; + } + + bufferlist tok_bl; + tok_bl.append(p); + + uint64_t nonce; + utime_t expiration; + std::string swift_user; + + try { + /*const*/ auto iter = tok_bl.begin(); + + ::decode(swift_user, iter); + ::decode(nonce, iter); + ::decode(expiration, iter); + } catch (buffer::error& err) { + ldout(cct, 0) << "NOTICE: failed to decode token" << dendl; + throw -EINVAL; + } + + const utime_t now = ceph_clock_now(cct); + if (expiration < now) { + ldout(cct, 0) << "NOTICE: old timed out token was used now=" << now + << " token.expiration=" << expiration + << dendl; + return nullptr; + } + + RGWUserInfo user_info; + ret = rgw_get_user_info_by_swift(store, swift_user, user_info); + if (ret < 0) { + throw ret; + } + + ldout(cct, 10) << "swift_user=" << swift_user << dendl; + + const auto siter = user_info.swift_keys.find(swift_user); + if (siter == std::end(user_info.swift_keys)) { + return nullptr; + } + + const auto swift_key = siter->second; + + bufferlist local_tok_bl; + ret = build_token(swift_user, swift_key.key, nonce, expiration, local_tok_bl); + if (ret < 0) { + throw ret; + } + + if (local_tok_bl.length() != tok_bl.length()) { + ldout(cct, 0) << "NOTICE: tokens length mismatch:" + << " tok_bl.length()=" << tok_bl.length() + << " local_tok_bl.length()=" << local_tok_bl.length() + << dendl; + return nullptr; + } + + if (memcmp(local_tok_bl.c_str(), tok_bl.c_str(), + local_tok_bl.length()) != 0) { + char buf[local_tok_bl.length() * 2 + 1]; + + buf_to_hex(reinterpret_cast(local_tok_bl.c_str()), + local_tok_bl.length(), buf); + + ldout(cct, 0) << "NOTICE: tokens mismatch tok=" << buf << dendl; + return nullptr; + } + + return apl_factory->create_apl_local(cct, user_info, + extract_swift_subuser(swift_user)); +} + + void RGW_SWIFT_Auth_Get::execute() { int ret = -EPERM; diff --git a/src/rgw/rgw_swift_auth.h b/src/rgw/rgw_swift_auth.h index be835fc068abe..39438ea992c3f 100644 --- a/src/rgw/rgw_swift_auth.h +++ b/src/rgw/rgw_swift_auth.h @@ -6,10 +6,102 @@ #include "rgw_op.h" #include "rgw_rest.h" +#include "rgw_auth.h" #define RGW_SWIFT_TOKEN_EXPIRATION (15 * 60) -extern int rgw_swift_verify_signed_token(CephContext *cct, RGWRados *store, const char *token, RGWUserInfo& info, string *pswift_user); +/* TempURL: applier. */ +class RGWTempURLAuthApplier : public RGWLocalAuthApplier { +public: + RGWTempURLAuthApplier(CephContext * const cct, + const RGWUserInfo& user_info) + : RGWLocalAuthApplier(cct, user_info, RGWLocalAuthApplier::NO_SUBUSER) { + }; + + virtual void modify_request_state(req_state * s) const override; /* in/out */ + + struct Factory { + virtual ~Factory() {} + virtual aplptr_t create_apl_turl(CephContext * const cct, + const RGWUserInfo& user_info) const = 0; + }; +}; + +/* TempURL: engine */ +class RGWTempURLAuthEngine : public RGWAuthEngine { +protected: + /* const */ RGWRados * const store; + const req_state * const s; + const RGWTempURLAuthApplier::Factory * const apl_factory; + + /* Helper methods. */ + void get_owner_info(RGWUserInfo& owner_info) const; + bool is_expired(const std::string& expires) const; + std::string generate_signature(const string& key, + const string& method, + const string& path, + const string& expires) const; +public: + RGWTempURLAuthEngine(const req_state * const s, + /*const*/ RGWRados * const store, + const RGWTempURLAuthApplier::Factory * const apl_factory) + : RGWAuthEngine(s->cct), + store(store), + s(s), + apl_factory(apl_factory) { + } + + /* Interface implementations. */ + bool is_applicable() const noexcept override; + RGWAuthApplier::aplptr_t authenticate() const override; +}; + + +/* AUTH_rgwtk */ +class RGWSignedTokenAuthEngine : public RGWTokenBasedAuthEngine { +protected: + /* const */ RGWRados * const store; + const RGWLocalAuthApplier::Factory * apl_factory; +public: + RGWSignedTokenAuthEngine(CephContext * const cct, + /* const */RGWRados * const store, + const std::string token, + const RGWLocalAuthApplier::Factory * const apl_factory) + : RGWTokenBasedAuthEngine(cct, token), + store(store), + apl_factory(apl_factory) { + } + + bool is_applicable() const noexcept override; + RGWAuthApplier::aplptr_t authenticate() const override; +}; + + +/* External token */ +class RGWExternalTokenAuthEngine : public RGWTokenBasedAuthEngine { +protected: + /* const */ RGWRados * const store; + const RGWLocalAuthApplier::Factory * const apl_factory; +public: + //using RGWTokenBasedAuthEngine::RGWTokenBasedAuthEngine; + RGWExternalTokenAuthEngine(CephContext * const cct, + /* const */RGWRados * const store, + const Extractor& extr, + const RGWLocalAuthApplier::Factory * const apl_factory) + : RGWTokenBasedAuthEngine(cct, extr), + store(store), + apl_factory(apl_factory) { + } + + bool is_applicable() const noexcept override; + RGWAuthApplier::aplptr_t authenticate() const override; +}; + + +extern int rgw_swift_verify_signed_token(CephContext *cct, + RGWRados *store, + const char *token, + rgw_swift_auth_info& auth_info); class RGW_SWIFT_Auth_Get : public RGWOp { public: -- 2.39.5