]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: aws4: add AWS4 auth support for S3 Post Object API
authorJavier M. Mellid <jmunhoz@igalia.com>
Fri, 10 Mar 2017 12:55:21 +0000 (13:55 +0100)
committerRadoslaw Zarzynski <rzarzynski@mirantis.com>
Wed, 7 Jun 2017 10:43:14 +0000 (12:43 +0200)
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 <jmunhoz@igalia.com>
Signed-off-by: Radoslaw Zarzynski <rzarzynski@mirantis.com>
src/rgw/rgw_auth_s3.cc
src/rgw/rgw_auth_s3.h
src/rgw/rgw_rest_s3.cc

index bbb70c96cbd73a7774337bc27eec596ab85d5d17..56ce96b33354fbdefe6748253f8303caacdc64cb 100644 (file)
@@ -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<string, RGWAccessKey>::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<string, RGWAccessKey>::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];
 
index cbc0bee69af0c1fea710c9fd44e59ff7fb534009..b10a969f53cd83f5be45bec9be5f5063ddde6b13 100644 (file)
@@ -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
index 0b2550d704b2e0c0c8806af03b93c0ef6d395e89..61de6afcd2f449399f0650c3c1f89fd8edfbbf96 100644 (file)
@@ -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<string, RGWAccessKey> access_keys = user_info.access_keys;
+        map<string, RGWAccessKey>::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<rgw_aws4_auth>(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()