]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: add rgw::auth::keystone::EC2Engine.
authorRadoslaw Zarzynski <rzarzynski@mirantis.com>
Wed, 4 Jan 2017 19:22:13 +0000 (20:22 +0100)
committerRadoslaw Zarzynski <rzarzynski@mirantis.com>
Fri, 24 Mar 2017 15:55:13 +0000 (16:55 +0100)
Signed-off-by: Radoslaw Zarzynski <rzarzynski@mirantis.com>
Signed-off-by: Matt Benjamin <mbenjamin@redhat.com>
src/rgw/rgw_auth_keystone.cc
src/rgw/rgw_auth_keystone.h
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h

index d43d21d35b57d0994ba91d8e917c2aa9650c52e5..5712019ab266113ed22c05eced31b3e2129707fd 100644 (file)
@@ -7,6 +7,7 @@
 #include <errno.h>
 #include <fnmatch.h>
 
+#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<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 */
index 895942c8db1c571825b11ec4fcd75a0c10bdaedf..eae1012bec1a5177a9b53b78ccaace03803ede33 100644 (file)
@@ -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<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 */
index c6918cc8d8679c8ee568a97afaba07bf5734fd66..87799b64c5fe87637f149d96dfc65cd3212009fb 100644 (file)
@@ -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>(),
-                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));
index 9ad533a758a2dc5c7102d73f6cb1ffea18b1bf4f..19c2cac3eaece4c95468cb11104031e4a1d95ddc 100644 (file)
@@ -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<string> 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: