From: Pritha Srivastava Date: Sun, 29 Mar 2020 04:56:03 +0000 (+0530) Subject: rgw: adds code to check for client id and cert thumbprint X-Git-Tag: v15.2.9~122^2~4^2~8 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=6a5cadf226b66085b459e0e5313d7617a8ca4eaa;p=ceph.git rgw: adds code to check for client id and cert thumbprint while validating incoming web token in AssumeRoleWithWebIdentity. Signed-off-by: Pritha Srivastava (cherry picked from commit 75b4060c7732bb00ea0811771920658a6492b168) --- diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index abf95cf4f3a2..e229b0a95054 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -12,6 +12,8 @@ #include #include + + #include "ceph_ver.h" #include "common/Formatter.h" #include "common/utf8.h" @@ -32,7 +34,7 @@ #include "rgw_iam_policy_keywords.h" #include "rgw_sts.h" - +#include "rgw_rest_oidc_provider.h" #include #define dout_context g_ceph_context @@ -46,9 +48,82 @@ WebTokenEngine::is_applicable(const std::string& token) const noexcept return ! token.empty(); } +boost::optional +WebTokenEngine::get_provider(const string& role_arn, const string& iss) const +{ + string tenant; + auto r_arn = rgw::ARN::parse(role_arn); + if (r_arn) { + tenant = r_arn->account; + } + string idp_url = iss; + auto pos = idp_url.find("http://"); + if (pos == std::string::npos) { + pos = idp_url.find("https://"); + if (pos != std::string::npos) { + idp_url.erase(pos, 8); + } else { + pos = idp_url.find("www."); + if (pos != std::string::npos) { + idp_url.erase(pos, 4); + } + } + } else { + idp_url.erase(pos, 7); + } + auto provider_arn = rgw::ARN(idp_url, "oidc-provider", tenant); + string p_arn = provider_arn.to_string(); + RGWOIDCProvider provider(cct, ctl, p_arn, tenant); + auto ret = provider.get(); + if (ret < 0) { + return boost::none; + } + return provider; +} + +bool +WebTokenEngine::is_client_id_valid(vector& client_ids, const string& client_id) const +{ + for (auto it : client_ids) { + if (it == client_id) { + return true; + } + } + return false; +} + +bool +WebTokenEngine::is_cert_valid(const vector& thumbprints, const string& cert) const +{ + //calculate thumbprint of cert + std::unique_ptr certbio(BIO_new_mem_buf(cert.data(), cert.size()), BIO_free_all); + std::unique_ptr keybio(BIO_new(BIO_s_mem()), BIO_free_all); + string pw=""; + std::unique_ptr x_509cert(PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); + const EVP_MD* fprint_type = EVP_sha1(); + unsigned int fprint_size; + unsigned char fprint[EVP_MAX_MD_SIZE]; + + if (!X509_digest(x_509cert.get(), fprint_type, fprint, &fprint_size)) { + return false; + } + stringstream ss; + for (unsigned int i = 0; i < fprint_size; i++) { + ss << std::setfill('0') << std::setw(2) << std::hex << (0xFF & (unsigned int)fprint[i]); + } + std::string digest = ss.str(); + + for (auto& it : thumbprints) { + if (boost::iequals(it,digest)) { + return true; + } + } + return false; +} + //Offline validation of incoming Web Token which is a signed JWT (JSON Web Token) boost::optional -WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token) const +WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s) const { WebTokenEngine::token_t t; try { @@ -72,12 +147,23 @@ WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& t if (t.client_id.empty() && decoded.has_payload_claim("clientId")) { t.client_id = decoded.get_payload_claim("clientId").as_string(); } - + string role_arn = s->info.args.get("RoleArn"); + auto provider = get_provider(role_arn, t.iss); + if (! provider) { + throw -EACCES; + } + vector client_ids = provider->get_client_ids(); + vector thumbprints = provider->get_thumbprints(); + if (! client_ids.empty()) { + if (! is_client_id_valid(client_ids, t.client_id) && ! is_client_id_valid(client_ids, t.aud)) { + throw -EACCES; + } + } //Validate signature if (decoded.has_algorithm()) { auto& algorithm = decoded.get_algorithm(); try { - validate_signature(dpp, decoded, algorithm, t.iss); + validate_signature(dpp, decoded, algorithm, t.iss, thumbprints); } catch (...) { throw -EACCES; } @@ -99,7 +185,7 @@ WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& t } void -WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss) const +WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector& thumbprints) const { if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") { // Get certificate @@ -131,8 +217,20 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec if (parser.parse(val.str.c_str(), val.str.size())) { vector x5c; if (JSONDecoder::decode_json("x5c", x5c, &parser)) { - string cert = "-----BEGIN CERTIFICATE-----\n" + x5c[0] + "\n-----END CERTIFICATE-----"; - ldpp_dout(dpp, 20) << "Certificate is: " << cert.c_str() << dendl; + string cert; + bool found_valid_cert = false; + for (auto& it : x5c) { + cert = "-----BEGIN CERTIFICATE-----\n" + it + "\n-----END CERTIFICATE-----"; + ldpp_dout(dpp, 20) << "Certificate is: " << cert.c_str() << dendl; + if (is_cert_valid(thumbprints, cert)) { + found_valid_cert = true; + break; + } + found_valid_cert = true; + } + if (! found_valid_cert) { + throw -EINVAL; + } try { //verify method takes care of expired tokens also if (algorithm == "RS256") { @@ -223,7 +321,7 @@ WebTokenEngine::authenticate( const DoutPrefixProvider* dpp, } try { - t = get_from_jwt(dpp, token); + t = get_from_jwt(dpp, token, s); } catch (...) { return result_t::deny(-EACCES); diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index c0f36b473146..79333f5043ae 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -8,11 +8,13 @@ #include "rgw_sts.h" #include "rgw_web_idp.h" #include "jwt-cpp/jwt.h" +#include "rgw_oidc_provider.h" namespace rgw::auth::sts { class WebTokenEngine : public rgw::auth::Engine { CephContext* const cct; + RGWCtl* const ctl; using result_t = rgw::auth::Engine::result_t; using token_t = rgw::web_idp::WebTokenClaims; @@ -22,10 +24,16 @@ class WebTokenEngine : public rgw::auth::Engine { bool is_applicable(const std::string& token) const noexcept; + bool is_client_id_valid(vector& client_ids, const string& client_id) const; + + bool is_cert_valid(const vector& thumbprints, const string& cert) const; + + boost::optional get_provider(const string& role_arn, const string& iss) const; + boost::optional - get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token) const; + get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s) const; - void validate_signature (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss) const; + void validate_signature (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector& thumbprints) const; result_t authenticate(const DoutPrefixProvider* dpp, const std::string& token, @@ -33,9 +41,11 @@ class WebTokenEngine : public rgw::auth::Engine { public: WebTokenEngine(CephContext* const cct, + RGWCtl* const ctl, const rgw::auth::TokenExtractor* const extractor, const rgw::auth::WebIdentityApplier::Factory* const apl_factory) : cct(cct), + ctl(ctl), extractor(extractor), apl_factory(apl_factory) { } @@ -79,7 +89,7 @@ public: RGWCtl* const ctl) : ctl(ctl), implicit_tenant_context(implicit_tenant_context), - web_token_engine(cct, + web_token_engine(cct, ctl, static_cast(this), static_cast(this)) { /* When the constructor's body is being executed, all member engines