From: Alex Wojno Date: Tue, 29 Apr 2025 19:53:04 +0000 (+0000) Subject: rgw: check all JWKS for STS X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=6ca29223b35e83c7649c0fd6fbf604ce548aef73;p=ceph.git rgw: check all JWKS for STS Signed-off-by: Alex Wojno (cherry picked from commit b5e01424a51f870a7b467cafa56649699068f3e7) --- diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index 9eb9f9cd239..b0e55f1c2d4 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -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& 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& 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 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()); diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index befa93169b4..0f3f8c00070 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -53,7 +53,7 @@ class WebTokenEngine : public rgw::auth::Engine { std::tuple, boost::optional> 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& 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& thumbprints) const; void shutdown_ssl(const DoutPrefixProvider* dpp, SSL* ssl, SSL_CTX* ctx) const; public: