]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/auth: ignoring signatures for HTTP OPTIONS calls 55550/head
authorTobias Urdin <tobias.urdin@binero.se>
Tue, 6 Feb 2024 07:50:55 +0000 (07:50 +0000)
committerCasey Bodley <cbodley@redhat.com>
Mon, 12 Feb 2024 22:27:27 +0000 (17:27 -0500)
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: missing 'rgw/keystone: use secret key from EC2 for sigv4 streaming mode'
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 9879d339f9cc45068797170d45cced1e43221884..fced30f7740b07b1d2de659d7750441e98661ab3 100644 (file)
@@ -452,7 +452,8 @@ 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
 {
   using server_signature_t = VersionAbstractor::server_signature_t;
   boost::optional<rgw::keystone::TokenEnvelope> token;
@@ -464,12 +465,19 @@ 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 std::make_pair(t->get<0>(), 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 std::make_pair(t->get<0>(), 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;
@@ -541,7 +549,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
 {
@@ -560,10 +567,14 @@ 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);
+
   boost::optional<token_envelope_t> t;
   int failure_reason;
   std::tie(t, 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 c9addb0098ace69e116fb12667fbf72c2264b593..e68da934e49bf71d7b9cdda8fdaba49c85ffa311 100644 (file)
@@ -151,7 +151,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 ce5bcd05672f2c5deb2afff3641dc6d3e72f7108..5f838e33bb1c04a0cf8f5a65f79f6c247dba8d82 100644 (file)
@@ -5774,6 +5774,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_info,
+                                             k.subuser, boost::none, 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);