]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: check all JWKS for STS
authorAlex Wojno <awojno@bloomberg.net>
Tue, 29 Apr 2025 19:53:04 +0000 (19:53 +0000)
committerPritha Srivastava <prsrivas@redhat.com>
Mon, 11 Aug 2025 00:20:28 +0000 (05:50 +0530)
Signed-off-by: Alex Wojno <awojno@bloomberg.net>
(cherry picked from commit b5e01424a51f870a7b467cafa56649699068f3e7)

src/rgw/rgw_rest_sts.cc
src/rgw/rgw_rest_sts.h

index 9eb9f9cd239ba9d1e67499bbcf4a4c81650508e0..b0e55f1c2d4189a7244ebe96b2c5c6145191d068 100644 (file)
@@ -548,7 +548,7 @@ WebTokenEngine::connect_to_host_get_cert_chain(const DoutPrefixProvider* dpp, co
   return chain_pem;
 }
 
-void
+bool
 WebTokenEngine::validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const
 {
   try {
@@ -567,18 +567,43 @@ WebTokenEngine::validate_signature_using_n_e(const DoutPrefixProvider* dpp, cons
     }
   } catch (const std::exception& e) {
     ldpp_dout(dpp, 10) << std::string("Signature validation using n, e failed: ") + e.what() << dendl;
-    throw std::system_error(EACCES, std::system_category(), std::string("Signature validation using n, e failed: ") + e.what());
+    return false;
   }
   ldpp_dout(dpp, 10) << "Verified signature using n and e"<< dendl;
-  return;
+  return true;
+}
+
+bool WebTokenEngine::validate_cert_url(const DoutPrefixProvider* dpp, const std::string& cert_url,
+    const std::vector<std::string>& thumbprints) const
+{
+  // Fetch and verify cert according to https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
+  const auto hostname = get_top_level_domain_from_host(dpp, cert_url);
+  ldpp_dout(dpp, 20) << "Validating hostname: " << hostname << dendl;
+  const auto cert_chain = connect_to_host_get_cert_chain(dpp, hostname, 443);
+  std::string cert;
+  try {
+    cert = extract_last_certificate(dpp, cert_chain);
+    ldpp_dout(dpp, 20) << "last cert: " << cert << dendl;
+  } catch(const std::exception& e) {
+    ldpp_dout(dpp, 20) << "Extracting last cert of jwks uri failed with: " << e.what() << dendl;
+    return false;
+  }
+
+  if (!is_cert_valid(thumbprints, cert)) {
+    ldpp_dout(dpp, 20) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
+    return false;
+  }
+
+  return true;
 }
 
 void
 WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector<string>& thumbprints, optional_yield y) const
 {
   if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") {
-    string cert_url = get_cert_url(iss, dpp, y);
-    if (cert_url.empty()) {
+    const auto cert_url = get_cert_url(iss, dpp, y);
+    if (cert_url.empty() || !validate_cert_url(dpp, cert_url, thumbprints)) {
+      ldpp_dout(dpp, 5) << "Not able to validate JWKS url with registered thumbprints" << dendl;
       throw std::system_error(EINVAL, std::system_category());
     }
 
@@ -605,20 +630,16 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec
         for (auto &key : keys) {
           JSONParser k_parser;
           vector<string> x5c;
-          std::string use;
-          bool skip{false};
+          std::string use, kid;
           if (k_parser.parse(key.c_str(), key.size())) {
-            if (JSONDecoder::decode_json("use", use, &k_parser)) {
-              if (use == "enc") { //if key is for encryption then don't use x5c or n and e for signature validation
-                skip = true;
-              } else if (use == "sig") {
-                skip = false;
-              }
+            if (JSONDecoder::decode_json("kid", kid, &k_parser)) {
+              ldpp_dout(dpp, 20) << "Checking key id: " << kid << dendl;
             }
-            if (JSONDecoder::decode_json("x5c", x5c, &k_parser)) {
-              if (skip == true) {
+            if (JSONDecoder::decode_json("use", use, &k_parser) && use != "sig") {
                 continue;
-              }
+            }
+
+            if (JSONDecoder::decode_json("x5c", x5c, &k_parser)) {
               string cert;
               bool found_valid_cert = false;
               for (auto& it : x5c) {
@@ -629,9 +650,9 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec
                   break;
                 }
               }
-              if (! found_valid_cert) {
-                ldpp_dout(dpp, 0) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
-                throw std::system_error(EINVAL, std::system_category());
+              if (!found_valid_cert) {
+                ldpp_dout(dpp, 10) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
+                continue;
               }
               try {
                 //verify method takes care of expired tokens also
@@ -690,53 +711,35 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec
                   verifier.verify(decoded);
                   return;
                 } else {
-                  ldpp_dout(dpp, 0) << "Unsupported algorithm: " << algorithm << dendl;
-                  throw std::system_error(EINVAL, std::system_category());
+                  ldpp_dout(dpp, 5) << "Unsupported algorithm: " << algorithm << dendl;
                 }
               }
               catch (const std::exception& e) {
-                ldpp_dout(dpp, 0) << "Signature validation using x5c failed" << e.what() << dendl;
-                throw std::system_error(EACCES, std::system_category());
+                ldpp_dout(dpp, 10) << "Signature validation using x5c failed" << e.what() << dendl;
               }
             } else {
+              // Try bare key validation
+              ldpp_dout(dpp, 20) << "Trying bare key validation" << dendl;
+              std::string kty;
+              if (JSONDecoder::decode_json("kty", kty, &k_parser) && kty != "RSA") {
+                ldpp_dout(dpp, 10) << "Only RSA bare key validation is currently supported" << dendl;
+                continue;
+              }
+
               if (algorithm == "RS256" || algorithm == "RS384" || algorithm == "RS512") {
-                string n, e; //modulus and exponent
+                std::string n, e; //modulus and exponent
                 if (JSONDecoder::decode_json("n", n, &k_parser) && JSONDecoder::decode_json("e", e, &k_parser)) {
-                  if (skip == true) {
-                    continue;
-                  }
-                  //Fetch and verify cert according to https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html
-                  //and the same must be installed as part of create oidc provider in rgw
-                  //this can be made common to all types of keys(x5c, n&e), making thumbprint validation similar to
-                  //AWS
-                  std::string hostname = get_top_level_domain_from_host(dpp, cert_url);
-                  //connect to host and get back cert chain from it
-                  std::string cert_chain = connect_to_host_get_cert_chain(dpp, hostname, 443);
-                  std::string cert;
-                  try {
-                    cert = extract_last_certificate(dpp, cert_chain);
-                    ldpp_dout(dpp, 20) << "last cert: " << cert << dendl;
-                  } catch(const std::exception& e) {
-                    ldpp_dout(dpp, 20) << "Extracting last cert of jwks uri failed with: " << e.what() << dendl;
-                    throw std::system_error(EINVAL, std::system_category());
+                  if (validate_signature_using_n_e(dpp, decoded, algorithm, n, e)) {
+                    return;
                   }
-                  if (!is_cert_valid(thumbprints, cert)) {
-                    ldpp_dout(dpp, 20) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
-                    throw std::system_error(EINVAL, std::system_category());
-                  }
-                  validate_signature_using_n_e(dpp, decoded, algorithm, n, e);
-                  return;
                 }
-                ldpp_dout(dpp, 0) << "x5c not present or n, e not present" << dendl;
-                throw std::system_error(EINVAL, std::system_category());
-              } else {
-                throw std::system_error(EINVAL, std::system_category(), "Invalid algorithm: " + algorithm);
+                ldpp_dout(dpp, 10) << "Bare key parameters are not present for key" << dendl;
               }
             }
-            ldpp_dout(dpp, 0) << "Signature can not be validated with the input given in keys: "<< dendl;
-            throw std::system_error(EINVAL, std::system_category());
           } //end k_parser.parse
-        }//end for iterate through keys
+        } //end for iterate through keys
+        ldpp_dout(dpp, 0) << "Signature can not be validated with the JWKS present." << dendl;
+        throw std::system_error(EINVAL, std::system_category());
       } else { //end val->is_array
         ldpp_dout(dpp, 0) << "keys not present in JSON" << dendl;
         throw std::system_error(EINVAL, std::system_category());
index befa93169b4ead0e4077f8d8a96e6ca470177ba8..0f3f8c0007040c71f2d5915b6bb85110f447a52b 100644 (file)
@@ -53,7 +53,7 @@ class WebTokenEngine : public rgw::auth::Engine {
   std::tuple<boost::optional<WebTokenEngine::token_t>, boost::optional<WebTokenEngine::principal_tags_t>>
   get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s, optional_yield y) const;
 
void validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const;
bool validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const;
 
   void validate_signature (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string& algorithm, const std::string& iss, const std::vector<std::string>& thumbprints, optional_yield y) const;
 
@@ -69,6 +69,8 @@ class WebTokenEngine : public rgw::auth::Engine {
   std::string connect_to_host_get_cert_chain(const DoutPrefixProvider* dpp, const std::string& hostname, int port = 443) const;
   std::string get_top_level_domain_from_host(const DoutPrefixProvider* dpp, const std::string& hostname) const;
   std::string extract_last_certificate(const DoutPrefixProvider* dpp, const std::string& pem_chain) const;
+  bool validate_cert_url(const DoutPrefixProvider* dpp, const std::string& cert_url,
+      const std::vector<std::string>& thumbprints) const;
   void shutdown_ssl(const DoutPrefixProvider* dpp, SSL* ssl, SSL_CTX* ctx) const;
 
 public: