]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: implement RGWKeystoneAuthEngine.
authorRadoslaw Zarzynski <rzarzynski@mirantis.com>
Mon, 11 Apr 2016 18:43:19 +0000 (20:43 +0200)
committerRadoslaw Zarzynski <rzarzynski@mirantis.com>
Thu, 2 Jun 2016 13:30:38 +0000 (15:30 +0200)
Signed-off-by: Radoslaw Zarzynski <rzarzynski@mirantis.com>
src/common/config_opts.h
src/rgw/rgw_auth.cc
src/rgw/rgw_keystone.h
src/rgw/rgw_swift.cc

index ae0e20772a5ed2b62c88e93648d0e119a04f119b..8fbaa6b320689306ab62bfaf0addfb0f31d1a026 100644 (file)
@@ -1286,6 +1286,7 @@ OPTION(rgw_keystone_admin_project, OPT_STR, "")  // keystone admin user project
 OPTION(rgw_keystone_admin_domain, OPT_STR, "")  // keystone admin user domain
 OPTION(rgw_keystone_api_version, OPT_INT, 2) // Version of Keystone API to use (2 or 3)
 OPTION(rgw_keystone_accepted_roles, OPT_STR, "Member, admin")  // roles required to serve requests
+OPTION(rgw_keystone_accepted_admin_roles, OPT_STR, "") // list of roles allowing an user to gain admin privileges
 OPTION(rgw_keystone_token_cache_size, OPT_INT, 10000)  // max number of entries in keystone token cache
 OPTION(rgw_keystone_revocation_interval, OPT_INT, 15 * 60)  // seconds between tokens revocation check
 OPTION(rgw_keystone_verify_ssl, OPT_BOOL, true) // should we try to verify keystone's ssl
index 7a2e13f7b0e55b5cdbfa4508bc84ae68e2829980..793e998186fbee39d3a1e8bd398ba15c289d2f44 100644 (file)
@@ -6,7 +6,8 @@
 #include "rgw_user.h"
 #include "rgw_http_client.h"
 #include "rgw_keystone.h"
-#include "rgw_swift.h"
+
+#include "include/str_list.h"
 
 #define dout_subsys ceph_subsys_rgw
 
@@ -239,3 +240,197 @@ RGWAuthApplier::aplptr_t RGWAnonymousAuthEngine::authenticate() const
   return apl_factory->create_apl_local(cct, user_info,
                                        RGWLocalAuthApplier::NO_SUBUSER);
 }
+
+
+/* Keystone */
+class RGWKeystoneHTTPTransceiver : public RGWHTTPTransceiver {
+public:
+  RGWKeystoneHTTPTransceiver(CephContext * const cct,
+                             bufferlist * const token_body_bl)
+    : RGWHTTPTransceiver(cct, token_body_bl,
+                         cct->_conf->rgw_keystone_verify_ssl,
+                         { "X-Subject-Token" }) {
+  }
+
+  std::string get_subject_token() const {
+    try {
+      return get_header_value("X-Subject-Token");
+    } catch (std::out_of_range&) {
+      return header_value_t();
+    }
+  }
+};
+
+typedef RGWKeystoneHTTPTransceiver RGWValidateKeystoneToken;
+typedef RGWKeystoneHTTPTransceiver RGWGetKeystoneAdminToken;
+typedef RGWKeystoneHTTPTransceiver RGWGetRevokedTokens;
+
+
+bool RGWKeystoneAuthEngine::is_applicable() const noexcept
+{
+  if (false == RGWTokenBasedAuthEngine::is_applicable()) {
+    return false;
+  }
+
+  return false == cct->_conf->rgw_keystone_url.empty();
+}
+
+KeystoneToken RGWKeystoneAuthEngine::decode_pki_token(const std::string token) const
+{
+  bufferlist token_body_bl;
+  int ret = rgw_decode_b64_cms(cct, token, token_body_bl);
+  if (ret < 0) {
+    ldout(cct, 20) << "cannot decode pki token" << dendl;
+    throw ret;
+  } else {
+    ldout(cct, 20) << "successfully decoded pki token" << dendl;
+  }
+
+  KeystoneToken token_body;
+  ret = token_body.parse(cct, token, token_body_bl);
+  if (ret < 0) {
+    throw ret;
+  }
+
+  return token_body;
+}
+
+KeystoneToken RGWKeystoneAuthEngine::get_from_keystone(const std::string token) const
+{
+  bufferlist token_body_bl;
+  RGWValidateKeystoneToken validate(cct, &token_body_bl);
+
+  std::string url;
+  if (KeystoneService::get_keystone_url(cct, url) < 0) {
+    throw -EINVAL;
+  }
+
+  const auto keystone_version = KeystoneService::get_api_version();
+  if (keystone_version == KeystoneApiVersion::VER_2) {
+    url.append("v2.0/tokens/" + token);
+  } else if (keystone_version == KeystoneApiVersion::VER_3) {
+    url.append("v3/auth/tokens");
+    validate.append_header("X-Subject-Token", token);
+  }
+
+  std::string admin_token;
+  if (KeystoneService::get_keystone_admin_token(cct, admin_token) < 0) {
+    throw -EINVAL;
+  }
+
+  validate.append_header("X-Auth-Token", admin_token);
+  validate.set_send_length(0);
+
+  int ret = validate.process(url.c_str());
+  if (ret < 0) {
+    throw ret;
+  }
+  token_body_bl.append((char)0); // NULL terminate for debug output
+
+  ldout(cct, 20) << "received response: " << token_body_bl.c_str() << dendl;
+
+  KeystoneToken token_body;
+  ret = token_body.parse(cct, token, token_body_bl);
+  if (ret < 0) {
+    throw ret;
+  }
+
+  return token_body;
+}
+
+RGWRemoteAuthApplier::AuthInfo
+RGWKeystoneAuthEngine::get_creds_info(const KeystoneToken& token,
+                                      const std::vector<std::string>& admin_roles
+                                    ) const noexcept
+{
+  /* Check whether the user has an admin status. */
+  bool is_admin = false;
+  for (const auto admin_role : admin_roles) {
+    if (token.has_role(admin_role)) {
+      is_admin = true;
+      break;
+    }
+  }
+
+  return {
+    /* Suggested account name for the authenticated user. */
+    rgw_user(token.get_project_id()),
+    /* User's display name (aka real name). */
+    token.get_project_name(),
+    /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
+     * the access rights through the perm_mask. At least at this layer. */
+    RGW_PERM_FULL_CONTROL,
+    is_admin,
+  };
+}
+
+RGWAuthApplier::aplptr_t RGWKeystoneAuthEngine::authenticate() const
+{
+  KeystoneToken t;
+
+  /* This will be initialized on the first call to this method. In C++11 it's
+   * also thread-safe. */
+  static 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;
+  } roles(cct);
+
+  /* Token ID is a concept that makes dealing with PKI tokens more effective.
+   * Instead of storing several kilobytes, a short hash can be burried. */
+  const auto token_id = rgw_get_token_id(token);
+  ldout(cct, 20) << "token_id=" << token_id << dendl;
+
+  /* Check cache first. */
+  if (RGWKeystoneTokenCache::get_instance().find(token_id, t)) {
+    ldout(cct, 20) << "cached token.project.id=" << t.get_project_id()
+                   << dendl;
+      return apl_factory->create_apl_remote(cct,
+                                            get_creds_info(t, roles.admin));
+  }
+
+  /* Retrieve token. */
+  if (rgw_is_pki_token(token)) {
+    try {
+      t = decode_pki_token(token);
+    } catch (...) {
+      /* Last resort. */
+      t = get_from_keystone(token);
+    }
+  } else {
+    /* Can't decode, just go to the Keystone server for validation. */
+    t = get_from_keystone(token);
+  }
+
+  /* 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 nullptr;
+  }
+
+  /* Check for necessary roles. */
+  for (const auto role : 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;
+      RGWKeystoneTokenCache::get_instance().add(token_id, t);
+      return apl_factory->create_apl_remote(cct,
+                                            get_creds_info(t, roles.admin));
+    }
+  }
+
+  ldout(cct, 0) << "user does not hold a matching role; required roles: "
+                << g_conf->rgw_keystone_accepted_roles << dendl;
+
+  return nullptr;
+}
index 63501ffa6d959961a315fc1686d85e991e3c40d4..2449dae7a265bca202451aee9eef48bf91b178ad 100644 (file)
@@ -16,6 +16,13 @@ int rgw_decode_b64_cms(CephContext *cct,
                        bufferlist& bl);
 bool rgw_is_pki_token(const string& token);
 void rgw_get_token_id(const string& token, string& token_id);
+static inline std::string rgw_get_token_id(const string& token)
+{
+  std::string token_id;
+  rgw_get_token_id(token, token_id);
+
+  return token_id;
+}
 bool rgw_decode_pki_token(CephContext *cct,
                           const string& token,
                           bufferlist& bl);
index 7525d32c4271bc1a8bf066740826395963bbc8b3..4a835bf452ed7ed946acb6a03931e87ef7934be1 100644 (file)
@@ -106,29 +106,6 @@ int RGWSwift::validate_token(const char *token, struct rgw_swift_auth_info *info
 }
 
 
-class RGWKeystoneHTTPTransceiver : public RGWHTTPTransceiver {
-public:
-  RGWKeystoneHTTPTransceiver(CephContext * const cct,
-                             bufferlist * const token_body_bl)
-    : RGWHTTPTransceiver(cct, token_body_bl,
-                         cct->_conf->rgw_keystone_verify_ssl,
-                         { "X-Subject-Token" }) {
-  }
-
-  std::string get_subject_token() const {
-    try {
-      return get_header_value("X-Subject-Token");
-    } catch (std::out_of_range&) {
-      return header_value_t();
-    }
-  }
-};
-
-typedef RGWKeystoneHTTPTransceiver RGWValidateKeystoneToken;
-typedef RGWKeystoneHTTPTransceiver RGWGetKeystoneAdminToken;
-typedef RGWKeystoneHTTPTransceiver RGWGetRevokedTokens;
-
-
 int RGWSwift::check_revoked()
 {
   string url;