From: Javier M. Mellid Date: Fri, 10 Mar 2017 12:55:21 +0000 (+0100) Subject: rgw: aws4: add AWS4 auth support for S3 Post Object API X-Git-Tag: v12.1.0~155^2~68 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=ac1a3f4a71d209d1b8adf33d25a50d58881cb8d2;p=ceph.git rgw: aws4: add AWS4 auth support for S3 Post Object API RGW S3 supports HTTP POST requests so users can upload content directly. This patch adds AWS v4 to handle form data properly. Fixes: http://tracker.ceph.com/issues/18800 Signed-off-by: Javier M. Mellid Signed-off-by: Radoslaw Zarzynski --- diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index bbb70c96cbd7..56ce96b33354 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -356,18 +356,22 @@ void rgw_create_s3_v4_string_to_sign(CephContext *cct, const string& algorithm, * calculate the AWS signature version 4 */ int rgw_calculate_s3_v4_aws_signature(struct req_state *s, - const string& access_key_id, const string &date, const string& region, - const string& service, const string& string_to_sign, string& signature) { + const string& access_key_id, const string &date, const string& region, const string& service, + const string& string_to_sign, string& signature, const string &access_key_secret) { - map::iterator iter = s->user->access_keys.find(access_key_id); - if (iter == s->user->access_keys.end()) { - ldout(s->cct, 10) << "ERROR: access key not encoded in user info" << dendl; - return -EPERM; - } - - RGWAccessKey& k = iter->second; + string secret_key = "AWS4"; - string secret_key = "AWS4" + k.key; + if (access_key_secret.empty()) { + map::iterator iter = s->user->access_keys.find(access_key_id); + if (iter == s->user->access_keys.end()) { + ldout(s->cct, 10) << "ERROR: access key not encoded in user info" << dendl; + return -EPERM; + } + RGWAccessKey& k = iter->second; + secret_key.append(k.key); + } else { + secret_key.append(access_key_secret); + } char secret_k[secret_key.size() * MAX_UTF8_SZ]; diff --git a/src/rgw/rgw_auth_s3.h b/src/rgw/rgw_auth_s3.h index cbc0bee69af0..b10a969f53cd 100644 --- a/src/rgw/rgw_auth_s3.h +++ b/src/rgw/rgw_auth_s3.h @@ -167,6 +167,6 @@ void rgw_create_s3_v4_string_to_sign(CephContext *cct, const string& algorithm, int rgw_calculate_s3_v4_aws_signature(struct req_state *s, const string& access_key_id, const string &date, const string& region, const string& service, const string& string_to_sign, - string& signature); + string& signature, const std::string& access_key_secret=""); #endif diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 0b2550d704b2..61de6afcd2f4 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -1656,46 +1656,173 @@ int RGWPostObj_ObjStore_S3::get_params() int RGWPostObj_ObjStore_S3::get_policy() { if (part_bl(parts, "policy", &s->auth.s3_postobj_creds.encoded_policy)) { - // check that the signature matches the encoded policy - if (!part_str(parts, "AWSAccessKeyId", - &s->auth.s3_postobj_creds.access_key)) { - ldout(s->cct, 0) << "No S3 access key found!" << dendl; - err_msg = "Missing access key"; - return -EINVAL; + bool aws4_auth = false; + + /* x-amz-algorithm handling */ + string x_amz_algorithm; + if ((part_str(parts, "x-amz-algorithm", &x_amz_algorithm)) && + (x_amz_algorithm.compare("AWS4-HMAC-SHA256") == 0)) { + ldout(s->cct, 0) << "Signature verification algorithm AWS v4 (AWS4-HMAC-SHA256)" << dendl; + aws4_auth = true; + } else { + ldout(s->cct, 0) << "Signature verification algorithm AWS v2" << dendl; } - if (!part_str(parts, "signature", &s->auth.s3_postobj_creds.signature)) { - ldout(s->cct, 0) << "No signature found!" << dendl; - err_msg = "Missing signature"; - return -EINVAL; - } + // check that the signature matches the encoded policy - /* FIXME: this is a makeshift solution. The browser upload authentication will be - * handled by an instance of rgw::auth::Completer spawned in Handler's authorize() - * method. */ - const auto& strategy = auth_registry_ptr->get_s3_post(); - try { - auto result = strategy.authenticate(s); - if (result.get_status() != decltype(result)::Status::GRANTED) { + if (aws4_auth) { + + /* AWS4 */ + + string s3_access_key; + + string received_credential_str; + string date_cs; + string region_cs; + string service_cs; + + string received_signature_str; + string received_date_str; + + /* x-amz-credential handling */ + if (!part_str(parts, "x-amz-credential", &received_credential_str)) { + ldout(s->cct, 0) << "No S3 aws4 credential found!" << dendl; + err_msg = "Missing aws4 credential"; + return -EINVAL; + } + size_t pos; + string cs_aux = received_credential_str; + /* s3_access_key */ + s3_access_key = cs_aux; + pos = s3_access_key.find("/"); + s3_access_key = s3_access_key.substr(0, pos); + cs_aux = cs_aux.substr(pos + 1, cs_aux.length()); + /* date cred */ + date_cs = cs_aux; + pos = date_cs.find("/"); + date_cs = date_cs.substr(0, pos); + cs_aux = cs_aux.substr(pos + 1, cs_aux.length()); + /* region cred */ + region_cs = cs_aux; + pos = region_cs.find("/"); + region_cs = region_cs.substr(0, pos); + cs_aux = cs_aux.substr(pos + 1, cs_aux.length()); + /* service cred */ + service_cs = cs_aux; + pos = service_cs.find("/"); + service_cs = service_cs.substr(0, pos); + /* x-amz-signature handling */ + if (!part_str(parts, "x-amz-signature", &received_signature_str)) { + ldout(s->cct, 0) << "No aws4 signature found!" << dendl; + err_msg = "Missing aws4 signature"; + return -EINVAL; + } + /* x-amz-date handling */ + if (!part_str(parts, "x-amz-date", &received_date_str)) { + ldout(s->cct, 0) << "No aws4 date found!" << dendl; + err_msg = "Missing aws4 date"; + return -EINVAL; + } + + RGWUserInfo user_info; + op_ret = rgw_get_user_info_by_access_key(store, s3_access_key, user_info); + if (op_ret < 0) { return -EACCES; + } else { + map access_keys = user_info.access_keys; + map::const_iterator iter = access_keys.find(s3_access_key); + // We know the key must exist, since the user was returned by + // rgw_get_user_info_by_access_key, but it doesn't hurt to check! + if (iter == access_keys.end()) { + ldout(s->cct, 0) << "Secret key lookup failed!" << dendl; + err_msg = "No secret key for matching access key"; + return -EACCES; + } + string s3_secret_key = (iter->second).key; + + /* AWS4 */ + + try { + s->aws4_auth = std::unique_ptr(new rgw_aws4_auth); + } catch (std::bad_alloc&) { + return -ENOMEM; + } + + std::string encoded_policy_str(s->auth.s3_postobj_creds.encoded_policy.c_str(), + s->auth.s3_postobj_creds.encoded_policy.length()); + std::string new_signature_str; + + int err = rgw_calculate_s3_v4_aws_signature(s, s3_access_key, + date_cs, region_cs, service_cs, + encoded_policy_str, new_signature_str, + s3_secret_key); + if (err) { + return err; + } + + ldout(s->cct, 10) << "----------------------------- Verifying signatures" << dendl; + ldout(s->cct, 10) << "Signature = " << received_signature_str << dendl; + ldout(s->cct, 10) << "New Signature = " << new_signature_str << dendl; + ldout(s->cct, 10) << "-----------------------------" << dendl; + + if (received_signature_str != new_signature_str) { + return -ERR_SIGNATURE_NO_MATCH; + } + } + + /* Deep copy. */ + *(s->user) = user_info; + s->owner.set_id(user_info.user_id); + s->owner.set_name(user_info.display_name); + + /* FIXME: remove this after switching S3 to the new authentication + * infrastructure. */ + s->auth.identity = rgw::auth::transform_old_authinfo(s); + } else { + + /* AWS2 */ + + // check that the signature matches the encoded policy + if (!part_str(parts, "AWSAccessKeyId", + &s->auth.s3_postobj_creds.access_key)) { + ldout(s->cct, 0) << "No S3 aws2 access key found!" << dendl; + err_msg = "Missing aws2 access key"; + return -EINVAL; + } + + if (!part_str(parts, "signature", &s->auth.s3_postobj_creds.signature)) { + ldout(s->cct, 0) << "No aws2 signature found!" << dendl; + err_msg = "Missing aws2 signature"; + return -EINVAL; } + /* FIXME: this is a makeshift solution. The browser upload authentication will be + * handled by an instance of rgw::auth::Completer spawned in Handler's authorize() + * method. */ + const auto& strategy = auth_registry_ptr->get_s3_post(); try { - auto applier = result.get_applier(); + auto result = strategy.authenticate(s); + if (result.get_status() != decltype(result)::Status::GRANTED) { + return -EACCES; + } + + try { + auto applier = result.get_applier(); - applier->load_acct_info(*s->user); - s->perm_mask = applier->get_perm_mask(); - applier->modify_request_state(s); - s->auth.identity = std::move(applier); + applier->load_acct_info(*s->user); + s->perm_mask = applier->get_perm_mask(); + applier->modify_request_state(s); + s->auth.identity = std::move(applier); - s->owner.set_id(s->user->user_id); - s->owner.set_name(s->user->display_name); - /* OK, fall through. */ + s->owner.set_id(s->user->user_id); + s->owner.set_name(s->user->display_name); + /* OK, fall through. */ + } catch (int err) { + return -EACCES; + } } catch (int err) { return -EACCES; } - } catch (int err) { - return -EACCES; } ldout(s->cct, 0) << "Successful Signature Verification!" << dendl; @@ -1722,9 +1849,15 @@ int RGWPostObj_ObjStore_S3::get_policy() return -EINVAL; } - post_policy.set_var_checked("AWSAccessKeyId"); + if (aws4_auth) { + /* AWS4 */ + post_policy.set_var_checked("x-amz-signature"); + } else { + /* AWS2 */ + post_policy.set_var_checked("AWSAccessKeyId"); + post_policy.set_var_checked("signature"); + } post_policy.set_var_checked("policy"); - post_policy.set_var_checked("signature"); r = post_policy.check(&env, err_msg); if (r < 0) { @@ -3055,7 +3188,7 @@ RGWOp *RGWHandler_REST_Obj_S3::op_post() if (s->info.args.exists("uploads")) return new RGWInitMultipart_ObjStore_S3; - return NULL; + return new RGWPostObj_ObjStore_S3; } RGWOp *RGWHandler_REST_Obj_S3::op_options()