#include <errno.h>
#include <fnmatch.h>
+#include "rgw_b64.h"
#include "common/errno.h"
#include "common/ceph_json.h"
#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"
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<std::string> plain;
+ std::vector<std::string> 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 */
#define CEPH_RGW_AUTH_KEYSTONE_H
#include "rgw_auth.h"
+#include "rgw_rest_s3.h"
#include "rgw_common.h"
#include "rgw_keystone.h"
}
}; /* 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<std::string>& 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 */
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>(),
- 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));