]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/auth: Add service token logic for Keystone
authorTobias Urdin <tobias.urdin@binero.se>
Tue, 15 Mar 2022 10:51:34 +0000 (10:51 +0000)
committerTobias Urdin <tobias.urdin@binero.se>
Wed, 21 Sep 2022 20:33:29 +0000 (20:33 +0000)
Signed-off-by: Tobias Urdin <tobias.urdin@binero.com>
src/rgw/rgw_auth_keystone.cc
src/rgw/rgw_auth_keystone.h

index 10d8d82738860e6ae1dca24fcc91c14639d97d3a..0b3dcbe03463b07fd2abaef9c108805103922781 100644 (file)
@@ -38,7 +38,7 @@ TokenEngine::is_applicable(const std::string& token) const noexcept
 }
 
 boost::optional<TokenEngine::token_envelope_t>
-TokenEngine::get_from_keystone(const DoutPrefixProvider* dpp, const std::string& token) const
+TokenEngine::get_from_keystone(const DoutPrefixProvider* dpp, const std::string& token, const bool& allow_expired) const
 {
   /* Unfortunately, we can't use the short form of "using" here. It's because
    * we're aliasing a class' member, not namespace. */
@@ -60,6 +60,11 @@ TokenEngine::get_from_keystone(const DoutPrefixProvider* dpp, const std::string&
     url.append("v2.0/tokens/" + token);
   } else if (keystone_version == rgw::keystone::ApiVersion::VER_3) {
     url.append("v3/auth/tokens");
+
+    if (allow_expired) {
+      url.append("?allow_expired=1");
+    }
+
     validate.append_header("X-Subject-Token", token);
   }
 
@@ -188,8 +193,10 @@ TokenEngine::get_acl_strategy(const TokenEngine::token_envelope_t& token) const
 TokenEngine::result_t
 TokenEngine::authenticate(const DoutPrefixProvider* dpp,
                           const std::string& token,
+                          const std::string& service_token,
                           const req_state* const s) const
 {
+  bool allow_expired = false;
   boost::optional<TokenEngine::token_envelope_t> t;
 
   /* This will be initialized on the first call to this method. In C++11 it's
@@ -228,10 +235,63 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp,
     return result_t::grant(std::move(apl));
   }
 
-  /* Not in cache. Go to the Keystone for validation. This happens even
+  /* TODO(tobias-urdin): Add config option for enabling service token support */
+
+  /* We have a service token and a token so we verify the service
+   * token and if it's invalid the request is invalid. If it's valid
+   * we allow an expired token to be used when doing lookup in Keystone.
+   * We never get to this if the token is in the cache. */
+  if (! service_token.empty()) {
+    boost::optional<TokenEngine::token_envelope_t> st;
+
+    const auto& service_token_id = rgw_get_token_id(service_token);
+    ldpp_dout(dpp, 20) << "service_token_id=" << service_token_id << dendl;
+
+    /* Check cache for service token first. */
+    st = token_cache.find(service_token_id);
+    if (st) {
+      ldpp_dout(dpp, 20) << "cached service_token.project.id=" << st->get_project_id()
+                     << dendl;
+
+      /* We found the service token in the cache so we allow using an expired
+       * token for this request. */
+      allow_expired = true;
+      ldpp_dout(dpp, 20) << "allowing expired tokens because service_token_id="
+                     << service_token_id
+                     << " was found in cache" << dendl;
+    } else {
+      /* Service token was not found in cache. Go to Keystone for validating
+       * the token. The allow_expired here must always be false. */
+      st = get_from_keystone(dpp, service_token, allow_expired);
+
+      if (! st) {
+        return result_t::deny(-EACCES);
+      }
+
+      /* Verify expiration of service token. */
+      if (st->expired()) {
+        ldpp_dout(dpp, 0) << "got expired service token: " << st->get_project_name()
+                       << ":" << st->get_user_name()
+                       << " expired " << st->get_expires() << dendl;
+        return result_t::deny(-EPERM);
+      }
+
+      /* TODO(tobias-urdin): Verify role on service user with corresponding config option */
+
+      /* Service token is valid so we allow using an expired token for
+       * this request. */
+      ldpp_dout(dpp, 20) << "allowing expired tokens because service_token_id="
+                     << service_token_id
+                     << " is valid" << dendl;
+      allow_expired = true;
+      token_cache.add(service_token_id, *st);
+    }
+  }
+
+  /* Token not in cache. Go to the Keystone for validation. This happens even
    * for the legacy PKI/PKIz token types. That's it, after the PKI/PKIz
    * RadosGW-side validation has been removed, we always ask Keystone. */
-  t = get_from_keystone(dpp, token);
+  t = get_from_keystone(dpp, token, allow_expired);
 
   if (! t) {
     return result_t::deny(-EACCES);
@@ -239,10 +299,17 @@ TokenEngine::authenticate(const DoutPrefixProvider* dpp,
 
   /* Verify expiration. */
   if (t->expired()) {
-    ldpp_dout(dpp, 0) << "got expired token: " << t->get_project_name()
-                  << ":" << t->get_user_name()
-                  << " expired: " << t->get_expires() << dendl;
-    return result_t::deny(-EPERM);
+    if (allow_expired) {
+      ldpp_dout(dpp, 20) << "allowing expired token: " << t->get_project_name()
+                    << ":" << t->get_user_name()
+                    << " expired: " << t->get_expires()
+                    << " because of valid service token" << dendl;
+    } else {
+      ldpp_dout(dpp, 0) << "got expired token: " << t->get_project_name()
+                    << ":" << t->get_user_name()
+                    << " expired: " << t->get_expires() << dendl;
+      return result_t::deny(-EPERM);
+    }
   }
 
   /* Check for necessary roles. */
index b986fc2a6aef6f7d9eaa47f60f344bbe2d05fbbd..6d463bb8617d4b035a4c031b3f7be048e300c36f 100644 (file)
@@ -39,7 +39,7 @@ class TokenEngine : public rgw::auth::Engine {
   bool is_applicable(const std::string& token) const noexcept;
 
   boost::optional<token_envelope_t>
-  get_from_keystone(const DoutPrefixProvider* dpp, const std::string& token) const;
+  get_from_keystone(const DoutPrefixProvider* dpp, const std::string& token, const bool& allow_expired) const;
 
   acl_strategy_t get_acl_strategy(const token_envelope_t& token) const;
   auth_info_t get_creds_info(const token_envelope_t& token,
@@ -47,6 +47,7 @@ class TokenEngine : public rgw::auth::Engine {
                             ) const noexcept;
   result_t authenticate(const DoutPrefixProvider* dpp,
                         const std::string& token,
+                        const std::string& service_token,
                         const req_state* s) const;
 
 public:
@@ -68,7 +69,7 @@ public:
 
   result_t authenticate(const DoutPrefixProvider* dpp, const req_state* const s,
                        optional_yield y) const override {
-    return authenticate(dpp, extractor->get_token(s), s);
+    return authenticate(dpp, extractor->get_token(s), extractor->get_service_token(s), s);
   }
 }; /* class TokenEngine */