diff options
author | Roald J. van Loon <roaldvanloon@gmail.com> | 2013-08-09 13:31:10 +0200 |
---|---|---|
committer | Yehuda Sadeh <yehuda@inktank.com> | 2013-08-31 17:43:26 -0700 |
commit | a200e184b15a03a4ca382e94caf01efb41cb9db7 (patch) | |
tree | dc7af74bd126783b588d77bb037dceaf0758b719 | |
parent | 9636722a67f43604768191302d93a91523e17b4f (diff) | |
download | ceph-a200e184b15a03a4ca382e94caf01efb41cb9db7.tar.gz |
Validate S3 tokens against Keystone
- 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>
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/common/config_opts.h | 2 | ||||
-rw-r--r-- | src/rgw/rgw_auth_s3.cc | 4 | ||||
-rw-r--r-- | src/rgw/rgw_json_enc.cc | 68 | ||||
-rw-r--r-- | src/rgw/rgw_keystone.cc | 107 | ||||
-rw-r--r-- | src/rgw/rgw_keystone.h | 106 | ||||
-rw-r--r-- | src/rgw/rgw_rest_s3.cc | 223 | ||||
-rw-r--r-- | src/rgw/rgw_rest_s3.h | 52 | ||||
-rw-r--r-- | src/rgw/rgw_swift.cc | 200 | ||||
-rw-r--r-- | src/rgw/rgw_swift.h | 20 |
10 files changed, 514 insertions, 272 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 3bc5cbe0e11..3bdec278c6f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -404,7 +404,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 @@ -2309,6 +2310,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\ diff --git a/src/common/config_opts.h b/src/common/config_opts.h index f526f80c929..328f7f4b94d 100644 --- a/src/common/config_opts.h +++ b/src/common/config_opts.h @@ -656,6 +656,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 diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index 2b0685118b1..b8246b784fa 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -79,9 +79,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; } /* diff --git a/src/rgw/rgw_json_enc.cc b/src/rgw/rgw_json_enc.cc index 05d7206ba44..189e9ae961e 100644 --- a/src/rgw/rgw_json_enc.cc +++ b/src/rgw/rgw_json_enc.cc @@ -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 index 00000000000..bb5091e2d1e --- /dev/null +++ b/src/rgw/rgw_keystone.cc @@ -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 index 00000000000..05199eef89d --- /dev/null +++ b/src/rgw/rgw_keystone.h @@ -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 diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 8690dd8fdbe..71bb418c53c 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -2020,6 +2020,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 */ @@ -2032,6 +2098,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()) { @@ -2061,75 +2134,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); diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index b0d3c30384a..e62334b9585 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -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); diff --git a/src/rgw/rgw_swift.cc b/src/rgw/rgw_swift.cc index b62033b2764..24e09051320 100644 --- a/src/rgw/rgw_swift.cc +++ b/src/rgw/rgw_swift.cc @@ -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) diff --git a/src/rgw/rgw_swift.h b/src/rgw/rgw_swift.h index febc2675c27..3f0bd161946 100644 --- a/src/rgw/rgw_swift.h +++ b/src/rgw/rgw_swift.h @@ -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; |