]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
Validate S3 tokens against Keystone
authorRoald J. van Loon <roaldvanloon@gmail.com>
Fri, 9 Aug 2013 11:31:10 +0000 (13:31 +0200)
committerSage Weil <sage@inktank.com>
Fri, 13 Dec 2013 22:42:32 +0000 (14:42 -0800)
- Added config option to allow S3 to use Keystone auth
- Implemented JSONDecoder for KeystoneToken
- RGW_Auth_S3::authorize now uses rgw_store_user_info on keystone auth
- Minor fix in get_canon_resource; dout is now after the assignment

Reviewed-by: Yehuda Sadeh<yehuda@inktank.com>
Signed-off-by: Roald J. van Loon <roaldvanloon@gmail.com>
(cherry picked from commit a200e184b15a03a4ca382e94caf01efb41cb9db7)

Conflicts:
src/rgw/rgw_swift.h

src/Makefile.am
src/common/config_opts.h
src/rgw/rgw_auth_s3.cc
src/rgw/rgw_json_enc.cc
src/rgw/rgw_keystone.cc [new file with mode: 0644]
src/rgw/rgw_keystone.h [new file with mode: 0644]
src/rgw/rgw_rest_s3.cc
src/rgw/rgw_rest_s3.h
src/rgw/rgw_swift.cc
src/rgw/rgw_swift.h

index 4138cfe52c6c56a718f9acbcbd3b9adf7b51de65..7909874170e13ea802dd0acfb0abc730656f355f 100644 (file)
@@ -401,7 +401,8 @@ librgw_a_SOURCES =  \
        rgw/rgw_cors_s3.cc \
         rgw/rgw_auth_s3.cc \
        rgw/rgw_metadata.cc \
-       rgw/rgw_replica_log.cc
+       rgw/rgw_replica_log.cc \
+       rgw/rgw_keystone.cc
 librgw_a_CFLAGS = ${CRYPTO_CFLAGS} ${AM_CFLAGS}
 librgw_a_CXXFLAGS = -Woverloaded-virtual ${AM_CXXFLAGS}
 noinst_LIBRARIES += librgw.a
@@ -2247,6 +2248,7 @@ noinst_HEADERS = \
        rgw/rgw_usage.h\
        rgw/rgw_user.h\
        rgw/rgw_bucket.h\
+       rgw/rgw_keystone.h\
         sample.ceph.conf\
        tools/common.h\
        test/osd/RadosModel.h\
index 91e82346b0cf0137883ffd1074af6b5e43b4bb17..ce73fb36454815084effeca37aa4b2965af4e63a 100644 (file)
@@ -640,6 +640,8 @@ OPTION(rgw_keystone_admin_token, OPT_STR, "")  // keystone admin token (shared s
 OPTION(rgw_keystone_accepted_roles, OPT_STR, "Member, admin")  // roles required to serve requests
 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_s3_auth_use_rados, OPT_BOOL, true)  // should we try to use the internal credentials for s3?
+OPTION(rgw_s3_auth_use_keystone, OPT_BOOL, false)  // should we try to use keystone for s3?
 OPTION(rgw_admin_entry, OPT_STR, "admin")  // entry point for which a url is considered an admin request
 OPTION(rgw_enforce_swift_acls, OPT_BOOL, true)
 OPTION(rgw_swift_token_expiration, OPT_INT, 24 * 3600) // time in seconds for swift token expiration
index f3f0c8322f05fbbf894dbccb88257628434ddfef..5f8fa4cc1a927c1a1730a3b57e9ac1c88f1ab3d8 100644 (file)
@@ -73,9 +73,9 @@ static void get_canon_resource(const char *request_uri, map<string, string>& sub
   if (!append_str.empty()) {
     s.append(append_str);
   }
-  dout(10) << "get_canon_resource(): dest=" << dest << dendl;
-
   dest = s;
+
+  dout(10) << "get_canon_resource(): dest=" << dest << dendl;
 }
 
 /*
index 05d7206ba44281364f68ed4d87ee152670b6e2ae..189e9ae961e55aae3826c7ec664e43bad2ab3be0 100644 (file)
@@ -6,6 +6,7 @@
 #include "rgw_acl_s3.h"
 #include "rgw_cache.h"
 #include "rgw_bucket.h"
+#include "rgw_keystone.h"
 
 #include "common/ceph_json.h"
 #include "common/Formatter.h"
@@ -704,3 +705,70 @@ void RGWDataChangesLogInfo::decode_json(JSONObj *obj)
   JSONDecoder::decode_json("last_update", last_update, obj);
 }
 
+void KeystoneToken::Metadata::decode_json(JSONObj *obj)
+{
+  JSONDecoder::decode_json("is_admin", is_admin, obj);
+}
+
+void KeystoneToken::Service::Endpoint::decode_json(JSONObj *obj)
+{
+  JSONDecoder::decode_json("id", id, obj);
+  JSONDecoder::decode_json("adminURL", admin_url, obj);
+  JSONDecoder::decode_json("publicURL", public_url, obj);
+  JSONDecoder::decode_json("internalURL", internal_url, obj);
+  JSONDecoder::decode_json("region", region, obj);
+}
+
+void KeystoneToken::Service::decode_json(JSONObj *obj)
+{
+  JSONDecoder::decode_json("type", type, obj, true);
+  JSONDecoder::decode_json("name", name, obj, true);
+  JSONDecoder::decode_json("endpoints", endpoints, obj);
+}
+
+void KeystoneToken::Token::Tenant::decode_json(JSONObj *obj)
+{
+  JSONDecoder::decode_json("id", id, obj, true);
+  JSONDecoder::decode_json("name", name, obj, true);
+  JSONDecoder::decode_json("description", description, obj);
+  JSONDecoder::decode_json("enabled", enabled, obj);
+}
+
+void KeystoneToken::Token::decode_json(JSONObj *obj)
+{
+  string expires_iso8601;
+  struct tm t;
+
+  JSONDecoder::decode_json("id", id, obj, true);
+  JSONDecoder::decode_json("tenant", tenant, obj, true);
+  JSONDecoder::decode_json("expires", expires_iso8601, obj, true);
+
+  if (parse_iso8601(expires_iso8601.c_str(), &t)) {
+    expires = timegm(&t);
+  } else {
+    expires = 0;
+    throw JSONDecoder::err("Failed to parse ISO8601 expiration date from Keystone response.");
+  }
+}
+
+void KeystoneToken::User::Role::decode_json(JSONObj *obj)
+{
+  JSONDecoder::decode_json("id", id, obj);
+  JSONDecoder::decode_json("name", name, obj);
+}
+
+void KeystoneToken::User::decode_json(JSONObj *obj)
+{
+  JSONDecoder::decode_json("id", id, obj, true);
+  JSONDecoder::decode_json("name", name, obj);
+  JSONDecoder::decode_json("username", user_name, obj, true);
+  JSONDecoder::decode_json("roles", roles, obj);
+}
+
+void KeystoneToken::decode_json(JSONObj *access_obj)
+{
+  JSONDecoder::decode_json("metadata", metadata, access_obj);
+  JSONDecoder::decode_json("token", token, access_obj, true);
+  JSONDecoder::decode_json("user", user, access_obj, true);
+  JSONDecoder::decode_json("serviceCatalog", service_catalog, access_obj);
+}
diff --git a/src/rgw/rgw_keystone.cc b/src/rgw/rgw_keystone.cc
new file mode 100644 (file)
index 0000000..bb5091e
--- /dev/null
@@ -0,0 +1,107 @@
+#include <errno.h>
+
+#include "common/errno.h"
+#include "common/ceph_json.h"
+#include "include/types.h"
+#include "include/str_list.h"
+
+#include "rgw_common.h"
+#include "rgw_keystone.h"
+
+#define dout_subsys ceph_subsys_rgw
+
+bool KeystoneToken::User::has_role(const string& r) {
+  list<Role>::iterator iter;
+  for (iter = roles.begin(); iter != roles.end(); ++iter) {
+      if (r.compare((*iter).name) == 0) {
+        return true;
+      }
+  }
+  return false;
+}
+
+int KeystoneToken::parse(CephContext *cct, bufferlist& bl)
+{
+  JSONParser parser;
+  if (!parser.parse(bl.c_str(), bl.length())) {
+    ldout(cct, 0) << "Keystone token parse error: malformed json" << dendl;
+    return -EINVAL;
+  }
+
+  try {
+    JSONDecoder::decode_json("access", *this, &parser);
+  } catch (JSONDecoder::err& err) {
+    ldout(cct, 0) << "Keystone token parse error: " << err.message << dendl;
+    return -EINVAL;
+  }
+
+  return 0;
+}
+
+bool RGWKeystoneTokenCache::find(const string& token_id, KeystoneToken& token)
+{
+  lock.Lock();
+  map<string, token_entry>::iterator iter = tokens.find(token_id);
+  if (iter == tokens.end()) {
+    lock.Unlock();
+    if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_miss);
+    return false;
+  }
+
+  token_entry& entry = iter->second;
+  tokens_lru.erase(entry.lru_iter);
+
+  if (entry.token.expired()) {
+    tokens.erase(iter);
+    lock.Unlock();
+    if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit);
+    return false;
+  }
+  token = entry.token;
+
+  tokens_lru.push_front(token_id);
+  entry.lru_iter = tokens_lru.begin();
+
+  lock.Unlock();
+  if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit);
+
+  return true;
+}
+
+void RGWKeystoneTokenCache::add(const string& token_id, KeystoneToken& token)
+{
+  lock.Lock();
+  map<string, token_entry>::iterator iter = tokens.find(token_id);
+  if (iter != tokens.end()) {
+    token_entry& e = iter->second;
+    tokens_lru.erase(e.lru_iter);
+  }
+
+  tokens_lru.push_front(token_id);
+  token_entry& entry = tokens[token_id];
+  entry.token = token;
+  entry.lru_iter = tokens_lru.begin();
+
+  while (tokens_lru.size() > max) {
+    list<string>::reverse_iterator riter = tokens_lru.rbegin();
+    iter = tokens.find(*riter);
+    assert(iter != tokens.end());
+    tokens.erase(iter);
+    tokens_lru.pop_back();
+  }
+
+  lock.Unlock();
+}
+
+void RGWKeystoneTokenCache::invalidate(const string& token_id)
+{
+  Mutex::Locker l(lock);
+  map<string, token_entry>::iterator iter = tokens.find(token_id);
+  if (iter == tokens.end())
+    return;
+
+  ldout(cct, 20) << "invalidating revoked token id=" << token_id << dendl;
+  token_entry& e = iter->second;
+  tokens_lru.erase(e.lru_iter);
+  tokens.erase(iter);
+}
diff --git a/src/rgw/rgw_keystone.h b/src/rgw/rgw_keystone.h
new file mode 100644 (file)
index 0000000..05199ee
--- /dev/null
@@ -0,0 +1,106 @@
+#ifndef CEPH_RGW_KEYSTONE_H
+#define CEPH_RGW_KEYSTONE_H
+
+#include "rgw_common.h"
+
+class KeystoneToken {
+public:
+  class Metadata {
+  public:
+    Metadata() : is_admin(false) { };
+    bool is_admin;
+    void decode_json(JSONObj *obj);
+  };
+
+  class Service {
+  public:
+    class Endpoint {
+    public:
+      string id;
+      string admin_url;
+      string public_url;
+      string internal_url;
+      string region;
+      void decode_json(JSONObj *obj);
+    };
+    string type;
+    string name;
+    list<Endpoint> endpoints;
+    void decode_json(JSONObj *obj);
+  };
+
+  class Token {
+  public:
+    Token() : expires(0) { };
+    class Tenant {
+    public:
+      Tenant() : enabled(false) { };
+      string id;
+      string name;
+      string description;
+      bool enabled;
+      void decode_json(JSONObj *obj);
+    };
+    string id;
+    time_t expires;
+    Tenant tenant;
+    void decode_json(JSONObj *obj);
+  };
+
+  class User {
+  public:
+    class Role {
+    public:
+      string id;
+      string name;
+      void decode_json(JSONObj *obj);
+    };
+    string id;
+    string name;
+    string user_name;
+    list<Role> roles;
+    void decode_json(JSONObj *obj);
+    bool has_role(const string& r);
+  };
+
+  Metadata metadata;
+  list<Service> service_catalog;
+  Token token;
+  User user;
+
+public:
+  int parse(CephContext *cct, bufferlist& bl);
+
+  bool expired() {
+    uint64_t now = ceph_clock_now(NULL).sec();
+    return (now >= (uint64_t)token.expires);
+  }
+
+  void decode_json(JSONObj *access_obj);
+};
+
+struct token_entry {
+  KeystoneToken token;
+  list<string>::iterator lru_iter;
+};
+
+class RGWKeystoneTokenCache {
+  CephContext *cct;
+
+  map<string, token_entry> tokens;
+  list<string> tokens_lru;
+
+  Mutex lock;
+
+  size_t max;
+
+public:
+  RGWKeystoneTokenCache(CephContext *_cct, int _max) : cct(_cct), lock("RGWKeystoneTokenCache"), max(_max) {}
+
+  bool find(const string& token_id, KeystoneToken& token);
+  void add(const string& token_id, KeystoneToken& token);
+  void invalidate(const string& token_id);
+};
+
+
+#endif
index 3da66f36a3aec153179b6ea3fc3ab828b251dbb1..ba6b4e7f34c3a543097f7050efd64f3d089512e7 100644 (file)
@@ -1965,6 +1965,72 @@ int RGWHandler_ObjStore_S3::init(RGWRados *store, struct req_state *s, RGWClient
 }
 
 
+/*
+ * 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("/");
+  keystone_url.append("v2.0/s3tokens");
+
+  /* set required headers for keystone request */
+  append_header("X-Auth-Token", cct->_conf->rgw_keystone_admin_token);
+  append_header("Content-Type", "application/json");
+
+  /* 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;
+  }
+
+  /* now parse response */
+  if (response.parse(cct, rx_buffer) < 0) {
+    dout(2) << "s3 keystone: token parsing failed" << dendl;
+    return -EPERM;
+  }
+
+  /* check if we have a valid role */
+  bool found = false;
+  list<string>::iterator iter;
+  for (iter = roles_list.begin(); iter != roles_list.end(); ++iter) {
+    if ((found=response.user.has_role(*iter))==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 -EPERM;
+  }
+
+  /* everything seems fine, continue with this user */
+  ldout(cct, 5) << "s3 keystone: validated token: " << response.token.tenant.name << ":" << response.user.name << " expires: " << response.token.expires << dendl;
+  return 0;
+}
+
 /*
  * verify that a signed request comes from the keyholder
  * by checking the signature against our locally-computed version
@@ -1978,6 +2044,13 @@ int RGW_Auth_S3::authorize(RGWRados *store, struct req_state *s)
   time_t now;
   time(&now);
 
+  /* neither keystone and rados enabled; warn and exit! */
+  if (!store->ctx()->_conf->rgw_s3_auth_use_rados
+      && !store->ctx()->_conf->rgw_s3_auth_use_keystone) {
+    dout(0) << "WARNING: no authorization backend enabled! Users will never authenticate." << dendl;
+    return -EPERM;
+  }
+
   if (!s->http_auth || !(*s->http_auth)) {
     auth_id = s->info.args.get("AWSAccessKeyId");
     if (auth_id.size()) {
@@ -2007,75 +2080,113 @@ int RGW_Auth_S3::authorize(RGWRados *store, struct req_state *s)
     auth_sign = auth_str.substr(pos + 1);
   }
 
-  /* first get the user info */
-  if (rgw_get_user_info_by_access_key(store, auth_id, s->user) < 0) {
-    dout(5) << "error reading user info, uid=" << auth_id << " can't authenticate" << dendl;
-    return -EPERM;
-  }
+  /* try keystone auth first */
+  int keystone_result = -EINVAL;
+  if (store->ctx()->_conf->rgw_s3_auth_use_keystone
+      && !store->ctx()->_conf->rgw_keystone_url.empty()) {
+    dout(20) << "s3 keystone: trying keystone auth" << dendl;
 
-  /* now verify signature */
-   
-  string auth_hdr;
-  if (!rgw_create_s3_canonical_header(s->info, &s->header_time, auth_hdr, qsr)) {
-    dout(10) << "failed to create auth header\n" << auth_hdr << dendl;
-    return -EPERM;
-  }
-  dout(10) << "auth_hdr:\n" << auth_hdr << dendl;
+    RGW_Auth_S3_Keystone_ValidateToken keystone_validator(store->ctx());
+    string token;
 
-  time_t req_sec = s->header_time.sec();
-  if ((req_sec < now - RGW_AUTH_GRACE_MINS * 60 ||
-      req_sec > now + RGW_AUTH_GRACE_MINS * 60) && !qsr) {
-    dout(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;
-    dout(0) << "NOTICE: request time skew too big now=" << utime_t(now, 0) << " req_time=" << s->header_time << dendl;
-    return -ERR_REQUEST_TIME_SKEWED;
-  }
+    if (!rgw_create_s3_canonical_header(s->info, &s->header_time, token, qsr)) {
+        dout(10) << "failed to create auth header\n" << token << dendl;
+    } else {
+      keystone_result = keystone_validator.validate_s3token(auth_id, token, auth_sign);
+      if (keystone_result == 0) {
+        s->user.user_id = keystone_validator.response.token.tenant.id;
+        s->user.display_name = keystone_validator.response.token.tenant.name; // wow.
+
+        /* try to store user if it not already exists */
+        if (rgw_get_user_info_by_uid(store, keystone_validator.response.token.tenant.id, s->user) < 0) {
+          int ret = rgw_store_user_info(store, s->user, NULL, NULL, 0, true);
+          if (ret < 0)
+            dout(10) << "NOTICE: failed to store new user's info: ret=" << ret << dendl;
+        }
 
-  map<string, RGWAccessKey>::iterator iter = s->user.access_keys.find(auth_id);
-  if (iter == s->user.access_keys.end()) {
-    dout(0) << "ERROR: access key not encoded in user info" << dendl;
-    return -EPERM;
+        s->perm_mask = RGW_PERM_FULL_CONTROL;
+      }
+    }
   }
-  RGWAccessKey& k = iter->second;
 
-  if (!k.subuser.empty()) {
-    map<string, RGWSubUser>::iterator uiter = s->user.subusers.find(k.subuser);
-    if (uiter == s->user.subusers.end()) {
-      dout(0) << "NOTICE: could not find subuser: " << k.subuser << dendl;
+  /* keystone failed (or not enabled); check if we want to use rados backend */
+  if (!store->ctx()->_conf->rgw_s3_auth_use_rados
+      && keystone_result < 0)
+    return keystone_result;
+
+  /* now try rados backend, but only if keystone did not succeed */
+  if (keystone_result < 0) {
+    /* get the user info */
+    if (rgw_get_user_info_by_access_key(store, auth_id, s->user) < 0) {
+      dout(5) << "error reading user info, uid=" << auth_id << " can't authenticate" << dendl;
       return -EPERM;
     }
-    RGWSubUser& subuser = uiter->second;
-    s->perm_mask = subuser.perm_mask;
-  } else
-    s->perm_mask = RGW_PERM_FULL_CONTROL;
 
-  string digest;
-  int ret = rgw_get_s3_header_digest(auth_hdr, k.key, digest);
-  if (ret < 0) {
-    return -EPERM;
-  }
+    /* now verify signature */
 
-  dout(15) << "calculated digest=" << digest << dendl;
-  dout(15) << "auth_sign=" << auth_sign << dendl;
-  dout(15) << "compare=" << auth_sign.compare(digest) << dendl;
+    string auth_hdr;
+    if (!rgw_create_s3_canonical_header(s->info, &s->header_time, auth_hdr, qsr)) {
+      dout(10) << "failed to create auth header\n" << auth_hdr << dendl;
+      return -EPERM;
+    }
+    dout(10) << "auth_hdr:\n" << auth_hdr << dendl;
+
+    time_t req_sec = s->header_time.sec();
+    if ((req_sec < now - RGW_AUTH_GRACE_MINS * 60 ||
+        req_sec > now + RGW_AUTH_GRACE_MINS * 60) && !qsr) {
+      dout(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;
+      dout(0) << "NOTICE: request time skew too big now=" << utime_t(now, 0) << " req_time=" << s->header_time << dendl;
+      return -ERR_REQUEST_TIME_SKEWED;
+    }
 
-  if (auth_sign != digest)
-    return -EPERM;
+    map<string, RGWAccessKey>::iterator iter = s->user.access_keys.find(auth_id);
+    if (iter == s->user.access_keys.end()) {
+      dout(0) << "ERROR: access key not encoded in user info" << dendl;
+      return -EPERM;
+    }
+    RGWAccessKey& k = iter->second;
 
-  if (s->user.system) {
-    s->system_request = true;
-    dout(20) << "system request" << dendl;
-    s->info.args.set_system();
-    string effective_uid = s->info.args.get(RGW_SYS_PARAM_PREFIX "uid");
-    RGWUserInfo effective_user;
-    if (!effective_uid.empty()) {
-      ret = rgw_get_user_info_by_uid(store, effective_uid, effective_user);
-      if (ret < 0) {
-        ldout(s->cct, 0) << "User lookup failed!" << dendl;
-        return -ENOENT;
+    if (!k.subuser.empty()) {
+      map<string, RGWSubUser>::iterator uiter = s->user.subusers.find(k.subuser);
+      if (uiter == s->user.subusers.end()) {
+        dout(0) << "NOTICE: could not find subuser: " << k.subuser << dendl;
+        return -EPERM;
       }
-      s->user = effective_user;
+      RGWSubUser& subuser = uiter->second;
+      s->perm_mask = subuser.perm_mask;
+    } else
+      s->perm_mask = RGW_PERM_FULL_CONTROL;
+
+    string digest;
+    int ret = rgw_get_s3_header_digest(auth_hdr, k.key, digest);
+    if (ret < 0) {
+      return -EPERM;
     }
-  }
+
+    dout(15) << "calculated digest=" << digest << dendl;
+    dout(15) << "auth_sign=" << auth_sign << dendl;
+    dout(15) << "compare=" << auth_sign.compare(digest) << dendl;
+
+    if (auth_sign != digest)
+      return -EPERM;
+
+    if (s->user.system) {
+      s->system_request = true;
+      dout(20) << "system request" << dendl;
+      s->info.args.set_system();
+      string effective_uid = s->info.args.get(RGW_SYS_PARAM_PREFIX "uid");
+      RGWUserInfo effective_user;
+      if (!effective_uid.empty()) {
+        ret = rgw_get_user_info_by_uid(store, effective_uid, effective_user);
+        if (ret < 0) {
+          ldout(s->cct, 0) << "User lookup failed!" << dendl;
+          return -ENOENT;
+        }
+        s->user = effective_user;
+      }
+    }
+
+  } /* if keystone_result < 0 */
 
   // populate the owner info
   s->owner.set_id(s->user.user_id);
index b0d3c30384ab9f34a22283a5b4b80608c9dda958..e62334b9585d034adef491dbf9f62c07d975f29b 100644 (file)
@@ -6,6 +6,7 @@
 #include "rgw_http_errors.h"
 #include "rgw_acl_s3.h"
 #include "rgw_policy_s3.h"
+#include "rgw_keystone.h"
 
 #define RGW_AUTH_GRACE_MINS 15
 
@@ -258,6 +259,57 @@ public:
   void end_response();
 };
 
+class RGW_Auth_S3_Keystone_ValidateToken : public RGWHTTPClient {
+private:
+  bufferlist rx_buffer;
+  bufferlist tx_buffer;
+  bufferlist::iterator tx_buffer_it;
+  list<string> roles_list;
+
+public:
+  KeystoneToken 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:
+  RGW_Auth_S3_Keystone_ValidateToken(CephContext *_cct)
+      : RGWHTTPClient(_cct) {
+    get_str_list(cct->_conf->rgw_keystone_accepted_roles, roles_list);
+  }
+
+  int receive_header(void *ptr, size_t len) {
+    return 0;
+  }
+  int receive_data(void *ptr, size_t len) {
+    rx_buffer.append((char *)ptr, len);
+    return 0;
+  }
+
+  int send_data(void *ptr, size_t len) {
+    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) {
+      assert(0);
+    }
+
+    return l;
+  }
+
+  int validate_s3token(const string& auth_id, const string& auth_token, const string& auth_sign);
+
+};
+
 class RGW_Auth_S3 {
 public:
   static int authorize(RGWRados *store, struct req_state *s);
index b62033b2764e342adf9b245f5f44c1fd3888948e..24e09051320d70ff460b60c75dad49d8b237b466 100644 (file)
@@ -8,6 +8,7 @@
 #include "rgw_swift_auth.h"
 #include "rgw_user.h"
 #include "rgw_http_client.h"
+#include "rgw_keystone.h"
 
 #include "include/str_list.h"
 
@@ -18,8 +19,6 @@
 
 static list<string> roles_list;
 
-class RGWKeystoneTokenCache;
-
 class RGWValidateSwiftToken : public RGWHTTPClient {
   struct rgw_swift_auth_info *info;
 
@@ -105,192 +104,7 @@ int RGWSwift::validate_token(const char *token, struct rgw_swift_auth_info *info
   return 0;
 }
 
-int KeystoneToken::parse(CephContext *cct, bufferlist& bl)
-{
-  JSONParser parser;
-
-  if (!parser.parse(bl.c_str(), bl.length())) {
-    ldout(cct, 0) << "malformed json" << dendl;
-    return -EINVAL;
-  }
-
-  JSONObjIter iter = parser.find_first("access");
-  if (iter.end()) {
-    ldout(cct, 0) << "token response is missing access section" << dendl;
-    return -EINVAL;
-  }  
-
-  JSONObj *access_obj = *iter;
-  JSONObj *user = access_obj->find_obj("user");
-  if (!user) {
-    ldout(cct, 0) << "token response is missing user section" << dendl;
-    return -EINVAL;
-  }
-
-  if (!user->get_data("username", &user_name)) {
-    ldout(cct, 0) << "token response is missing user username field" << dendl;
-    return -EINVAL;
-  }
-
-  JSONObj *roles_obj = user->find_obj("roles");
-  if (!roles_obj) {
-    ldout(cct, 0) << "token response is missing roles section, or section empty" << dendl;
-    return -EINVAL;
-  }
-
-  JSONObjIter riter = roles_obj->find_first();
-  if (riter.end()) {
-    ldout(cct, 0) << "token response has an empty roles list" << dendl;
-    return -EINVAL;
-  }
 
-  for (; !riter.end(); ++riter) {
-    JSONObj *role_obj = *riter;
-    if (!role_obj) {
-      ldout(cct, 0) << "ERROR: role object is NULL" << dendl;
-      return -EINVAL;
-    }
-
-    JSONObj *role_name = role_obj->find_obj("name");
-    if (!role_name) {
-      ldout(cct, 0) << "token response is missing role name section" << dendl;
-      return -EINVAL;
-    }
-    string role = role_name->get_data();
-    roles[role] = true;
-  }
-
-  JSONObj *token = access_obj->find_obj("token");
-  if (!token) {
-    ldout(cct, 0) << "missing token section in response" << dendl;
-    return -EINVAL;
-  }
-
-  string expires;
-
-  if (!token->get_data("expires", &expires)) {
-    ldout(cct, 0) << "token response is missing expiration field" << dendl;
-    return -EINVAL;
-  }
-
-  struct tm t;
-  if (!parse_iso8601(expires.c_str(), &t)) {
-    ldout(cct, 0) << "failed to parse token expiration (" << expires << ")" << dendl;
-    return -EINVAL;
-  }
-
-  expiration = timegm(&t);
-
-  JSONObj *tenant = token->find_obj("tenant");
-  if (!tenant) {
-    ldout(cct, 0) << "token response is missing tenant section" << dendl;
-    return -EINVAL;
-  }
-
-  if (!tenant->get_data("id", &tenant_id)) {
-    ldout(cct, 0) << "tenant is missing id field" << dendl;
-    return -EINVAL;
-  }
-
-
-  if (!tenant->get_data("name", &tenant_name)) {
-    ldout(cct, 0) << "tenant is missing name field" << dendl;
-    return -EINVAL;
-  }
-
-  return 0;
-}
-
-struct token_entry {
-  KeystoneToken token;
-  list<string>::iterator lru_iter;
-};
-
-class RGWKeystoneTokenCache {
-  CephContext *cct;
-
-  map<string, token_entry> tokens;
-  list<string> tokens_lru;
-
-  Mutex lock;
-
-  size_t max;
-
-public:
-  RGWKeystoneTokenCache(CephContext *_cct, int _max) : cct(_cct), lock("RGWKeystoneTokenCache"), max(_max) {}
-
-  bool find(const string& token_id, KeystoneToken& token);
-  void add(const string& token_id, KeystoneToken& token);
-  void invalidate(const string& token_id);
-};
-
-bool RGWKeystoneTokenCache::find(const string& token_id, KeystoneToken& token)
-{
-  lock.Lock();
-  map<string, token_entry>::iterator iter = tokens.find(token_id);
-  if (iter == tokens.end()) {
-    lock.Unlock();
-    if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_miss);
-    return false;
-  }
-
-  token_entry& entry = iter->second;
-  tokens_lru.erase(entry.lru_iter);
-
-  if (entry.token.expired()) {
-    tokens.erase(iter);
-    lock.Unlock();
-    if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit);
-    return false;
-  }
-  token = entry.token;
-
-  tokens_lru.push_front(token_id);
-  entry.lru_iter = tokens_lru.begin();
-
-  lock.Unlock();
-  if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit);
-
-  return true;
-}
-
-void RGWKeystoneTokenCache::add(const string& token_id, KeystoneToken& token)
-{
-  lock.Lock();
-  map<string, token_entry>::iterator iter = tokens.find(token_id);
-  if (iter != tokens.end()) {
-    token_entry& e = iter->second;
-    tokens_lru.erase(e.lru_iter);
-  }
-
-  tokens_lru.push_front(token_id);
-  token_entry& entry = tokens[token_id];
-  entry.token = token;
-  entry.lru_iter = tokens_lru.begin();
-
-  while (tokens_lru.size() > max) {
-    list<string>::reverse_iterator riter = tokens_lru.rbegin();
-    iter = tokens.find(*riter);
-    assert(iter != tokens.end());
-    tokens.erase(iter);
-    tokens_lru.pop_back();
-  }
-  
-  lock.Unlock();
-}
-
-void RGWKeystoneTokenCache::invalidate(const string& token_id)
-{
-  Mutex::Locker l(lock);
-  map<string, token_entry>::iterator iter = tokens.find(token_id);
-  if (iter == tokens.end())
-    return;
-
-  ldout(cct, 20) << "invalidating revoked token id=" << token_id << dendl;
-  token_entry& e = iter->second;
-  tokens_lru.erase(e.lru_iter);
-  tokens.erase(iter);
-}
 
 class RGWValidateKeystoneToken : public RGWHTTPClient {
   bufferlist *bl;
@@ -489,8 +303,8 @@ int RGWSwift::check_revoked()
 
 static void rgw_set_keystone_token_auth_info(KeystoneToken& token, struct rgw_swift_auth_info *info)
 {
-  info->user = token.tenant_id;
-  info->display_name = token.tenant_name;
+  info->user = token.token.tenant.id;
+  info->display_name = token.token.tenant.name;
   info->status = 200;
 }
 
@@ -504,10 +318,8 @@ int RGWSwift::parse_keystone_token_response(const string& token, bufferlist& bl,
   list<string>::iterator iter;
   for (iter = roles_list.begin(); iter != roles_list.end(); ++iter) {
     const string& role = *iter;
-    if (t.roles.find(role) != t.roles.end()) {
-      found = true;
+    if ((found=t.user.has_role(role))==true)
       break;
-    }
   }
 
   if (!found) {
@@ -515,7 +327,7 @@ int RGWSwift::parse_keystone_token_response(const string& token, bufferlist& bl,
     return -EPERM;
   }
 
-  ldout(cct, 0) << "validated token: " << t.tenant_name << ":" << t.user_name << " expires: " << t.expiration << dendl;
+  ldout(cct, 0) << "validated token: " << t.token.tenant.name << ":" << t.user.name << " expires: " << t.token.expires << dendl;
 
   rgw_set_keystone_token_auth_info(t, info);
 
@@ -592,7 +404,7 @@ int RGWSwift::validate_keystone_token(RGWRados *store, const string& token, stru
   if (keystone_token_cache->find(token_id, t)) {
     rgw_set_keystone_token_auth_info(t, info);
 
-    ldout(cct, 20) << "cached token.tenant_id=" << t.tenant_id << dendl;
+    ldout(cct, 20) << "cached token.tenant.id=" << t.token.tenant.id << dendl;
 
     int ret = update_user_info(store, info, rgw_user);
     if (ret < 0)
index cb00c40638aae5d406270caf4ac49cdfc0f50b29..3f0bd16194602e3c8f69350eec29bc0dd04b30b6 100644 (file)
@@ -6,6 +6,7 @@
 #include "common/Cond.h"
 
 class RGWRados;
+class KeystoneToken;
 
 struct rgw_swift_auth_info {
   int status;
@@ -17,25 +18,6 @@ struct rgw_swift_auth_info {
   rgw_swift_auth_info() : status(0), ttl(0) {}
 };
 
-class KeystoneToken {
-public:
-  string tenant_name;
-  string tenant_id;
-  string user_name;
-  time_t expiration;
-
-  map<string, bool> roles;
-
-  KeystoneToken() : expiration(0) {}
-
-  int parse(CephContext *cct, bufferlist& bl);
-
-  bool expired() {
-    uint64_t now = ceph_clock_now(NULL).sec();
-    return (now >= (uint64_t)expiration);
-  }
-};
-
 class RGWSwift {
   CephContext *cct;
   atomic_t down_flag;