From: Radoslaw Zarzynski Date: Wed, 4 Jan 2017 19:22:13 +0000 (+0100) Subject: rgw: add rgw::auth::keystone::EC2Engine. X-Git-Tag: v12.0.2~305^2~29 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=d9c723cf505bb6ba3e9166f478f0c46f84f89d1b;p=ceph.git rgw: add rgw::auth::keystone::EC2Engine. Signed-off-by: Radoslaw Zarzynski Signed-off-by: Matt Benjamin --- diff --git a/src/rgw/rgw_auth_keystone.cc b/src/rgw/rgw_auth_keystone.cc index d43d21d35b57..5712019ab266 100644 --- a/src/rgw/rgw_auth_keystone.cc +++ b/src/rgw/rgw_auth_keystone.cc @@ -7,6 +7,7 @@ #include #include +#include "rgw_b64.h" #include "common/errno.h" #include "common/ceph_json.h" @@ -17,6 +18,8 @@ #include "rgw_keystone.h" #include "rgw_auth_keystone.h" #include "rgw_keystone.h" +#include "rgw_rest_s3.h" +#include "rgw_auth_s3.h" #include "common/ceph_crypto_cms.h" #include "common/armor.h" @@ -273,6 +276,210 @@ TokenEngine::authenticate(const std::string& token) const return std::make_pair(nullptr, nullptr); } + +/* + * Try to validate S3 auth against keystone s3token interface + */ +rgw::keystone::TokenEnvelope +EC2Engine::get_from_keystone(const std::string& access_key_id, + const std::string& string_to_sign, + const std::string& signature) const +{ + /* prepare keystone url */ + std::string keystone_url = config.get_endpoint_url(); + if (keystone_url.empty()) { + throw -EINVAL; + } + + const auto api_version = config.get_api_version(); + if (config.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. */ + std::string admin_token; + int ret = rgw::keystone::Service::get_admin_token(cct, token_cache, config, + admin_token); + if (ret < 0) { + ldout(cct, 2) << "s3 keystone: cannot get token for keystone access" + << dendl; + throw ret; + } + + using RGWValidateKeystoneToken + = rgw::keystone::Service::RGWValidateKeystoneToken; + + /* The container for plain response obtained from Keystone. It will be + * parsed token_envelope_t::parse method. */ + ceph::bufferlist token_body_bl; + RGWValidateKeystoneToken validate(cct, &token_body_bl); + + /* set required headers for keystone request */ + validate.append_header("X-Auth-Token", admin_token); + validate.append_header("Content-Type", "application/json"); + + /* check if we want to verify keystone's ssl certs */ + validate.set_verify_ssl(cct->_conf->rgw_keystone_verify_ssl); + + /* create json credentials request body */ + JSONFormatter credentials(false); + credentials.open_object_section(""); + credentials.open_object_section("credentials"); + credentials.dump_string("access", access_key_id); + credentials.dump_string("token", rgw::to_base64(string_to_sign)); + credentials.dump_string("signature", signature); + credentials.close_section(); + credentials.close_section(); + + std::stringstream os; + credentials.flush(os); + validate.set_post_data(os.str()); + validate.set_send_length(os.str().length()); + + /* send request */ + ret = validate.process("POST", keystone_url.c_str()); + if (ret < 0) { + ldout(cct, 2) << "s3 keystone: token validation ERROR: " + << token_body_bl.c_str() << dendl; + throw -EPERM; + } + + /* if the supplied signature is wrong, we will get 401 from Keystone */ + if (validate.get_http_status() == + decltype(validate)::HTTP_STATUS_UNAUTHORIZED) { + throw -ERR_SIGNATURE_NO_MATCH; + } + + /* now parse response */ + rgw::keystone::TokenEnvelope token_envelope; + if (token_envelope.parse(cct, std::string(), token_body_bl, api_version) < 0) { + ldout(cct, 2) << "s3 keystone: token parsing failed" << dendl; + throw -EPERM; + } + + return std::move(token_envelope); +} + + +bool EC2Engine::is_time_skew_ok(const utime_t& header_time, + const bool qsr) const +{ + /* Check for time skew first. */ + const time_t req_sec = header_time.sec(); + time_t now; + time(&now); + + if ((req_sec < now - RGW_AUTH_GRACE_MINS * 60 || + req_sec > now + RGW_AUTH_GRACE_MINS * 60) && !qsr) { + ldout(cct, 10) << "req_sec=" << req_sec << " now=" << now + << "; now - RGW_AUTH_GRACE_MINS=" + << now - RGW_AUTH_GRACE_MINS * 60 + << "; now + RGW_AUTH_GRACE_MINS=" + << now + RGW_AUTH_GRACE_MINS * 60 + << dendl; + + ldout(cct, 0) << "NOTICE: request time skew too big now=" + << utime_t(now, 0) + << " req_time=" << header_time + << dendl; + return false; + } else { + return true; + } +} + +rgw::auth::Engine::result_t EC2Engine::authenticate(std::string access_key_id, + std::string signature, + std::string expires, + bool qsr, + const req_info& info) const +{ + /* This will be initialized on the first call to this method. In C++11 it's + * also thread-safe. */ + static const struct RolesCacher { + RolesCacher(CephContext* const cct) { + get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain); + get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin); + + /* Let's suppose that having an admin role implies also a regular one. */ + plain.insert(std::end(plain), std::begin(admin), std::end(admin)); + } + + std::vector plain; + std::vector admin; + } accepted_roles(cct); + + bool is_ok; + std::string string_to_sign; + utime_t header_time; + std::tie(is_ok, string_to_sign, header_time) = \ + rgw_create_s3_canonical_header(info, qsr); + if (! is_ok) { + dout(10) << "failed to create auth header\n" << string_to_sign << dendl; + throw -EPERM; + } + + const auto t = get_from_keystone(access_key_id, string_to_sign, + signature); + /* Verify expiration. */ + if (t.expired()) { + ldout(cct, 0) << "got expired token: " << t.get_project_name() + << ":" << t.get_user_name() + << " expired: " << t.get_expires() << dendl; + return std::make_pair(nullptr, nullptr); + } + + /* check if we have a valid role */ + bool found = false; + for (const auto& role : accepted_roles.plain) { + if (t.has_role(role) == true) { + found = true; + break; + } + } + + if (!found) { + ldout(cct, 5) << "s3 keystone: user does not hold a matching role;" + " required roles: " + << cct->_conf->rgw_keystone_accepted_roles << dendl; + } + + /* everything seems fine, continue with this user */ + ldout(cct, 5) << "s3 keystone: validated token: " << t.get_project_name() + << ":" << t.get_user_name() + << " expires: " << t.get_expires() << dendl; +#if 0 + return 0; +} + + /* Check for necessary roles. */ + for (const auto& role : accepted_roles.plain) { + if (t.has_role(role) == true) { + ldout(cct, 0) << "validated token: " << t.get_project_name() + << ":" << t.get_user_name() + << " expires: " << t.get_expires() << dendl; + + auto apl = apl_factory->create_apl_remote(cct, get_acl_strategy(t), + get_creds_info(t, roles.admin)); + return std::make_pair(std::move(apl), nullptr); + } + } + + ldout(cct, 0) << "user does not hold a matching role; required roles: " + << g_conf->rgw_keystone_accepted_roles << dendl; + + //return std::make_pair(nullptr, nullptr); +#endif + + if (! is_time_skew_ok(header_time, qsr)) { + throw -ERR_REQUEST_TIME_SKEWED; + } + + throw -ERR_INVALID_ACCESS_KEY; +} + }; /* namespace keystone */ }; /* namespace auth */ }; /* namespace rgw */ diff --git a/src/rgw/rgw_auth_keystone.h b/src/rgw/rgw_auth_keystone.h index 895942c8db1c..eae1012bec1a 100644 --- a/src/rgw/rgw_auth_keystone.h +++ b/src/rgw/rgw_auth_keystone.h @@ -6,6 +6,7 @@ #define CEPH_RGW_AUTH_KEYSTONE_H #include "rgw_auth.h" +#include "rgw_rest_s3.h" #include "rgw_common.h" #include "rgw_keystone.h" @@ -62,6 +63,55 @@ public: } }; /* class TokenEngine */ + +class EC2Engine : public rgw::auth::s3::Version2ndEngine { + using acl_strategy_t = rgw::auth::RemoteApplier::acl_strategy_t; + using auth_info_t = rgw::auth::RemoteApplier::AuthInfo; + using result_t = rgw::auth::Engine::result_t; + using token_envelope_t = rgw::keystone::TokenEnvelope; + + const rgw::auth::RemoteApplier::Factory* const apl_factory; + rgw::keystone::Config& config; + rgw::keystone::TokenCache& token_cache; + + /* Helper methods. */ + acl_strategy_t get_acl_strategy(const token_envelope_t& token) const; + auth_info_t get_creds_info(const token_envelope_t& token, + const std::vector& admin_roles + ) const noexcept; + bool is_time_skew_ok(const utime_t& header_time, + const bool qsr) const; + token_envelope_t get_from_keystone(const std::string& access_key_id, + const std::string& string_to_sign, + const std::string& signature) const; + result_t authenticate(std::string access_key_id, + std::string signature, + std::string expires, + bool qsr, + const req_info& info) const override; +public: + EC2Engine(CephContext* const cct, + const rgw::auth::s3::Version2ndEngine::Extractor* const extractor, + const rgw::auth::RemoteApplier::Factory* const apl_factory, + rgw::keystone::Config& config, + /* The token cache is used ONLY for the retrieving admin token. + * Due to the architecture of AWS Auth S3 credentials cannot be + * cached at all. */ + rgw::keystone::TokenCache& token_cache) + : Version2ndEngine(cct, *extractor), + apl_factory(apl_factory), + config(config), + token_cache(token_cache) { + } + + using Version2ndEngine::authenticate; + + const char* get_name() const noexcept override { + return "rgw::auth::keystone::EC2Engine"; + } + +}; /* class EC2Engine */ + }; /* namespace keystone */ }; /* namespace auth */ }; /* namespace rgw */ diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index c6918cc8d867..87799b64c5fe 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3291,108 +3291,6 @@ int RGWHandler_REST_S3::init(RGWRados *store, struct req_state *s, return RGWHandler_REST::init(store, s, cio); } -/* - * Try to validate S3 auth against keystone s3token interface - */ -int RGW_Auth_S3_Keystone_ValidateToken::validate_s3token( - const string& auth_id, const string& auth_token, const string& auth_sign) { - /* prepare keystone url */ - string keystone_url = cct->_conf->rgw_keystone_url; - if (keystone_url[keystone_url.size() - 1] != '/') { - keystone_url.append("/"); - } - - 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_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; - } - - /* set required headers for keystone request */ - append_header("X-Auth-Token", admin_token_id); - append_header("Content-Type", "application/json"); - - /* check if we want to verify keystone's ssl certs */ - set_verify_ssl(cct->_conf->rgw_keystone_verify_ssl); - - /* encode token */ - bufferlist token_buff; - bufferlist token_encoded; - token_buff.append(auth_token); - token_buff.encode_base64(token_encoded); - token_encoded.append((char)0); - - /* create json credentials request body */ - JSONFormatter credentials(false); - credentials.open_object_section(""); - credentials.open_object_section("credentials"); - credentials.dump_string("access", auth_id); - credentials.dump_string("token", token_encoded.c_str()); - credentials.dump_string("signature", auth_sign); - credentials.close_section(); - credentials.close_section(); - - std::stringstream os; - credentials.flush(os); - set_tx_buffer(os.str()); - - /* send request */ - int ret = process("POST", keystone_url.c_str()); - if (ret < 0) { - dout(2) << "s3 keystone: token validation ERROR: " << rx_buffer.c_str() - << dendl; - return -EPERM; - } - - /* if the supplied signature is wrong, we will get 401 from Keystone */ - if (get_http_status() == HTTP_STATUS_UNAUTHORIZED) { - return -ERR_SIGNATURE_NO_MATCH; - } - - /* now parse response */ - 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; - } - - /* check if we have a valid role */ - bool found = false; - for (const auto& role : accepted_roles) { - if (response.has_role(role) == true) { - found = true; - break; - } - } - - if (!found) { - ldout(cct, 5) << "s3 keystone: user does not hold a matching role;" - " required roles: " - << cct->_conf->rgw_keystone_accepted_roles << dendl; - return -ERR_INVALID_ACCESS_KEY; - } - - /* everything seems fine, continue with this user */ - ldout(cct, 5) << "s3 keystone: validated token: " << response.get_project_name() - << ":" << response.get_user_name() - << " expires: " << response.get_expires() << dendl; - return 0; -} - static void init_anon_user(struct req_state *s) { rgw_get_anon_user(*(s->user)); diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 9ad533a758a2..19c2cac3eaec 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -406,56 +406,6 @@ public: void send_response(); }; -class RGW_Auth_S3_Keystone_ValidateToken : public RGWHTTPClient { -private: - bufferlist rx_buffer; - bufferlist tx_buffer; - bufferlist::iterator tx_buffer_it; - vector accepted_roles; - -public: - rgw::keystone::TokenEnvelope response; - -private: - void set_tx_buffer(const string& d) { - tx_buffer.clear(); - tx_buffer.append(d); - tx_buffer_it = tx_buffer.begin(); - set_send_length(tx_buffer.length()); - } - -public: - explicit RGW_Auth_S3_Keystone_ValidateToken(CephContext *_cct) - : RGWHTTPClient(_cct) { - get_str_vec(cct->_conf->rgw_keystone_accepted_roles, accepted_roles); - } - - int receive_header(void *ptr, size_t len) override { - return 0; - } - int receive_data(void *ptr, size_t len) override { - rx_buffer.append((char *)ptr, len); - return 0; - } - - int send_data(void *ptr, size_t len) override { - if (!tx_buffer_it.get_remaining()) - return 0; // nothing left to send - - int l = MIN(tx_buffer_it.get_remaining(), len); - memcpy(ptr, tx_buffer_it.get_current_ptr().c_str(), l); - try { - tx_buffer_it.advance(l); - } catch (buffer::end_of_buffer &e) { - ceph_abort(); - } - - return l; - } - - int validate_s3token(const string& auth_id, const string& auth_token, const string& auth_sign); - -}; class RGW_Auth_S3 { private: