]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/auth: ignoring signatures for HTTP OPTIONS calls 60458/head
authorTobias Urdin <tobias.urdin@binero.se>
Tue, 6 Feb 2024 07:50:55 +0000 (07:50 +0000)
committerCasey Bodley <cbodley@redhat.com>
Wed, 23 Oct 2024 19:40:30 +0000 (15:40 -0400)
Before [1] we always sent all HTTP OPTIONS requests to
the S3AnonymousEngine and ignored any provided AWSv4
credentials sent in the request.

That PR changed so that if we got credentials in the
request we instead sent it through the authentication
code in order to solve HTTP OPTIONS requests on tenanted
users to start working (because we need to resolve the
tenant, also called bucket tenant in the code, and we can't
only rely on the bucket name since it will not be found).

We solved this by modifying the canonical HTTP method used
when calculating the AWSv4 signature by instead using the
access-control-request-method header which worked good.

This change did not take into account that when you generated
a presigned URL for a put_object request you can also pass in
extra parameters like a canned ACL [2] to the Params variable
in for example boto3's generated_presigned_url().

Doing that will cause the client to add the x-amz-acl header
to x-amz-signedheaders and also use that in their signature
calculation.

When doing a HTTP OPTIONS calls for CORS on that presigned URL
the browser will never send a x-amz-acl header with the correct
data since that is something that the actual PUT request should
include later, so that HTTP OPTIONS call should pass even though
the signature can never be calculated correctly server-side like
verified against AWS S3 in tracker [3].

This patch as a result skips the signature calculation when doing
EC2 auth using the LocalEngine but we still need to pass the request
there in order to lookup the user to support buckets in a tenant.

For the Keystone EC2 auth we're pretty out of luck in the sense that
Keystone's API itself requires us to send the AWSv4 signature in the
request with the access_key in order to obtain a token, and we cannot
leave the signature out, we also cannot spoof the signature from
rgw -> keystone since we don't have access to the secret_key if it's
not in our cache.

For that approach we simply pass on to get_access_token() that if it's
an HTTP OPTIONS and we find the access_key in the cache we pull that
and ignore verifying signature and pass it on for validation. This means
that the cache must be warm if using Keystone auth and adding extra
params to a presigned URL.

This partly makes some of the commits in [1] redundant for EC2
LocalEngine auth but we still need it for tenanted bucket support.

[1] https://github.com/ceph/ceph/pull/52673
[2] https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl
[3] https://tracker.ceph.com/issues/64308

Fixes: https://tracker.ceph.com/issues/64308
Signed-off-by: Tobias Urdin <tobias.urdin@binero.se>
(cherry picked from commit fe15b52edb5d228d2ed56679c62cf48493ae2d54)

Conflicts: no optional_yield argument in quincy
src/rgw/rgw_auth_keystone.cc
src/rgw/rgw_auth_keystone.h

src/rgw/rgw_auth_keystone.cc
src/rgw/rgw_auth_keystone.h
src/rgw/rgw_rest_s3.cc

index e6f4857cdef466b68faa63a61e4c1224c78a08f8..519f307e9874737147667469a25517e11f0c069b 100644 (file)
@@ -571,7 +571,8 @@ auto EC2Engine::get_access_token(const DoutPrefixProvider* dpp,
                                  const std::string_view& access_key_id,
                                  const std::string& string_to_sign,
                                  const std::string_view& signature,
-                                 const signature_factory_t& signature_factory) const
+                                 const signature_factory_t& signature_factory,
+                                 bool ignore_signature) const
     -> access_token_result
 {
   using server_signature_t = VersionAbstractor::server_signature_t;
@@ -585,12 +586,19 @@ auto EC2Engine::get_access_token(const DoutPrefixProvider* dpp,
 
   /* Check that credentials can correctly be used to sign data */
   if (t) {
-    std::string sig(signature);
-    server_signature_t server_signature = signature_factory(cct, t->get<1>(), string_to_sign);
-    if (sig.compare(server_signature) == 0) {
+    /* We should ignore checking signature in cache if caller tells us to which
+     * means we're handling a HTTP OPTIONS call. */
+    if (ignore_signature) {
+      ldpp_dout(dpp, 20) << "ignore_signature set and found in cache" << dendl;
       return {t->get<0>(), t->get<1>(), 0};
     } else {
-      ldpp_dout(dpp, 0) << "Secret string does not correctly sign payload, cache miss" << dendl;
+      std::string sig(signature);
+      server_signature_t server_signature = signature_factory(cct, t->get<1>(), string_to_sign);
+      if (sig.compare(server_signature) == 0) {
+        return {t->get<0>(), t->get<1>(), 0};
+      } else {
+        ldpp_dout(dpp, 0) << "Secret string does not correctly sign payload, cache miss" << dendl;
+      }
     }
   } else {
     ldpp_dout(dpp, 0) << "No stored secret string, cache miss" << dendl;
@@ -662,7 +670,6 @@ rgw::auth::Engine::result_t EC2Engine::authenticate(
   const string_to_sign_t& string_to_sign,
   const signature_factory_t& signature_factory,
   const completer_factory_t& completer_factory,
-  /* Passthorugh only! */
   const req_state* s,
   optional_yield y) const
 {
@@ -681,8 +688,12 @@ rgw::auth::Engine::result_t EC2Engine::authenticate(
     std::vector<std::string> admin;
   } accepted_roles(cct);
 
+  /* When we handle a HTTP OPTIONS call we must ignore the signature */
+  bool ignore_signature = (s->op_type == RGW_OP_OPTIONS_CORS);
+
   auto [t, secret_key, failure_reason] =
-    get_access_token(dpp, access_key_id, string_to_sign, signature, signature_factory);
+    get_access_token(dpp, access_key_id, string_to_sign,
+                     signature, signature_factory, ignore_signature);
   if (! t) {
     if (failure_reason == -ERR_SIGNATURE_NO_MATCH) {
       // we looked up a secret but it didn't generate the same signature as
index de1a108747c1821ffd5f54fb4e473a4dbeebcedd..111f44f29dc1208836f449169d5f5f3aefdc859f 100644 (file)
@@ -161,7 +161,8 @@ class EC2Engine : public rgw::auth::s3::AWSEngine {
                    const std::string_view& access_key_id,
                    const std::string& string_to_sign,
                    const std::string_view& signature,
-                  const signature_factory_t& signature_factory) const;
+                  const signature_factory_t& signature_factory,
+                   bool ignore_signature) const;
   result_t authenticate(const DoutPrefixProvider* dpp,
                         const std::string_view& access_key_id,
                         const std::string_view& signature,
index 99590bdaaa598dc5027e2ff840ade21a093e1433..4f91da033ff3ac3a150e004f35c35b986fa88ba0 100644 (file)
@@ -6180,6 +6180,13 @@ rgw::auth::s3::LocalEngine::authenticate(
   }
   const RGWAccessKey& k = iter->second;
 
+  /* Ignore signature for HTTP OPTIONS */
+  if (s->op_type == RGW_OP_OPTIONS_CORS) {
+    auto apl = apl_factory->create_apl_local(cct, s, user->get_info(),
+                                             k.subuser, std::nullopt, access_key_id);
+    return result_t::grant(std::move(apl), completer_factory(k.key));
+  }
+
   const VersionAbstractor::server_signature_t server_signature = \
     signature_factory(cct, k.key, string_to_sign);
   auto compare = signature.compare(server_signature);