summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoald J. van Loon <roaldvanloon@gmail.com>2013-08-09 13:31:10 +0200
committerYehuda Sadeh <yehuda@inktank.com>2013-08-31 17:43:26 -0700
commita200e184b15a03a4ca382e94caf01efb41cb9db7 (patch)
treedc7af74bd126783b588d77bb037dceaf0758b719
parent9636722a67f43604768191302d93a91523e17b4f (diff)
downloadceph-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.am4
-rw-r--r--src/common/config_opts.h2
-rw-r--r--src/rgw/rgw_auth_s3.cc4
-rw-r--r--src/rgw/rgw_json_enc.cc68
-rw-r--r--src/rgw/rgw_keystone.cc107
-rw-r--r--src/rgw/rgw_keystone.h106
-rw-r--r--src/rgw/rgw_rest_s3.cc223
-rw-r--r--src/rgw/rgw_rest_s3.h52
-rw-r--r--src/rgw/rgw_swift.cc200
-rw-r--r--src/rgw/rgw_swift.h20
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;