From 7566664f89be062e0c9f3519dc60b94c8af5e2a4 Mon Sep 17 00:00:00 2001 From: Pritha Srivastava Date: Thu, 26 Mar 2020 18:23:58 +0530 Subject: [PATCH] rgw: Added code for offline OpenId Connect/ OAuth 2.0 tokens if the token is a JWT. Signed-off-by: Pritha Srivastava --- src/rgw/rgw_auth.cc | 7 +- src/rgw/rgw_rest_sts.cc | 202 ++++++++++++++++++++++++++++++++-------- src/rgw/rgw_rest_sts.h | 7 +- src/rgw/rgw_web_idp.h | 4 +- 4 files changed, 175 insertions(+), 45 deletions(-) diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index 38d5c9091889c..46eddfcb08acc 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -363,10 +363,15 @@ void rgw::auth::WebIdentityApplier::modify_request_state(const DoutPrefixProvide s->info.args.append("sub", token_claims.sub); s->info.args.append("aud", token_claims.aud); s->info.args.append("provider_id", token_claims.iss); + s->info.args.append("client_id", token_claims.client_id); string idp_url = get_idp_url(); string condition = idp_url + ":app_id"; - s->env.emplace(condition, token_claims.aud); + if (! token_claims.client_id.empty()) { + s->env.emplace(condition, token_claims.client_id); + } else { + s->env.emplace(condition, token_claims.aud); + } } bool rgw::auth::WebIdentityApplier::is_identity(const idset_t& ids) const diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index 27715e57b0b53..abf95cf4f3a29 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -1,5 +1,10 @@ // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab ft=cpp +#include +#include +#include +#include +#include #include #include @@ -8,7 +13,6 @@ #include #include "ceph_ver.h" - #include "common/Formatter.h" #include "common/utf8.h" #include "common/ceph_json.h" @@ -16,6 +20,7 @@ #include "rgw_rest.h" #include "rgw_auth.h" #include "rgw_auth_registry.h" +#include "jwt-cpp/jwt.h" #include "rgw_rest_sts.h" #include "rgw_formats.h" @@ -28,10 +33,6 @@ #include "rgw_sts.h" -#include -#include -#include - #include #define dout_context g_ceph_context @@ -45,51 +46,169 @@ WebTokenEngine::is_applicable(const std::string& token) const noexcept return ! token.empty(); } +//Offline validation of incoming Web Token which is a signed JWT (JSON Web Token) boost::optional -WebTokenEngine::get_from_idp(const DoutPrefixProvider* dpp, const std::string& token) const +WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token) const +{ + WebTokenEngine::token_t t; + try { + const auto& decoded = jwt::decode(token); + + auto& payload = decoded.get_payload(); + ldpp_dout(dpp, 20) << " payload = " << payload << dendl; + if (decoded.has_issuer()) { + t.iss = decoded.get_issuer(); + } + if (decoded.has_audience()) { + auto aud = decoded.get_audience(); + t.aud = *(aud.begin()); + } + if (decoded.has_subject()) { + t.sub = decoded.get_subject(); + } + if (decoded.has_payload_claim("client_id")) { + t.client_id = decoded.get_payload_claim("client_id").as_string(); + } + if (t.client_id.empty() && decoded.has_payload_claim("clientId")) { + t.client_id = decoded.get_payload_claim("clientId").as_string(); + } + + //Validate signature + if (decoded.has_algorithm()) { + auto& algorithm = decoded.get_algorithm(); + try { + validate_signature(dpp, decoded, algorithm, t.iss); + } catch (...) { + throw -EACCES; + } + } else { + return boost::none; + } + } catch (int error) { + if (error == -EACCES) { + throw -EACCES; + } + ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl; + return boost::none; + } + catch (...) { + ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl; + return boost::none; + } + return t; +} + +void +WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss) const { - //Access token conforming to OAuth2.0 - if (! cct->_conf->rgw_sts_token_introspection_url.empty()) { - bufferlist introspect_resp; - RGWHTTPTransceiver introspect_req(cct, "POST", cct->_conf->rgw_sts_token_introspection_url, &introspect_resp); + if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") { + // Get certificate + string cert_url = iss + "/protocol/openid-connect/certs"; + bufferlist cert_resp; + RGWHTTPTransceiver cert_req(cct, "GET", cert_url, &cert_resp); //Headers - introspect_req.append_header("Content-Type", "application/x-www-form-urlencoded"); - string base64_creds = "Basic " + rgw::to_base64(cct->_conf->rgw_sts_client_id + ":" + cct->_conf->rgw_sts_client_secret); - introspect_req.append_header("Authorization", base64_creds); - // POST data - string post_data = "token=" + token; - introspect_req.set_post_data(post_data); - introspect_req.set_send_length(post_data.length()); - - int res = introspect_req.process(null_yield); + cert_req.append_header("Content-Type", "application/x-www-form-urlencoded"); + + int res = cert_req.process(null_yield); if (res < 0) { ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl; throw -EINVAL; } //Debug only - ldpp_dout(dpp, 20) << "HTTP status: " << introspect_req.get_http_status() << dendl; - ldpp_dout(dpp, 20) << "JSON Response is: " << introspect_resp.c_str() << dendl; + ldpp_dout(dpp, 20) << "HTTP status: " << cert_req.get_http_status() << dendl; + ldpp_dout(dpp, 20) << "JSON Response is: " << cert_resp.c_str() << dendl; JSONParser parser; - WebTokenEngine::token_t token; - if (!parser.parse(introspect_resp.c_str(), introspect_resp.length())) { - ldpp_dout(dpp, 2) << "Malformed json" << dendl; - throw -EINVAL; + if (parser.parse(cert_resp.c_str(), cert_resp.length())) { + JSONObj::data_val val; + if (parser.get_data("keys", &val)) { + if (val.str[0] == '[') { + val.str.erase(0, 1); + } + if (val.str[val.str.size() - 1] == ']') { + val.str = val.str.erase(val.str.size() - 1, 1); + } + 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; + try { + //verify method takes care of expired tokens also + if (algorithm == "RS256") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs256{cert}); + + verifier.verify(decoded); + } else if (algorithm == "RS384") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs384{cert}); + + verifier.verify(decoded); + } else if (algorithm == "RS512") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs512{cert}); + + verifier.verify(decoded); + } else if (algorithm == "ES256") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::es256{cert}); + + verifier.verify(decoded); + } else if (algorithm == "ES384") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::es384{cert}); + + verifier.verify(decoded); + } else if (algorithm == "ES512") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::es512{cert}); + + verifier.verify(decoded); + } else if (algorithm == "PS256") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::ps256{cert}); + + verifier.verify(decoded); + } else if (algorithm == "PS384") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::ps384{cert}); + + verifier.verify(decoded); + } else if (algorithm == "PS512") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::ps512{cert}); + + verifier.verify(decoded); + } + } catch (std::runtime_error& e) { + ldpp_dout(dpp, 0) << "Signature validation failed: " << e.what() << dendl; + throw; + } + catch (...) { + ldpp_dout(dpp, 0) << "Signature validation failed" << dendl; + throw; + } + } else { + ldpp_dout(dpp, 0) << "x5c not present" << dendl; + throw -EINVAL; + } + } else { + ldpp_dout(dpp, 0) << "Malformed JSON object for keys" << dendl; + throw -EINVAL; + } + } else { + ldpp_dout(dpp, 0) << "keys not present in JSON" << dendl; + throw -EINVAL; + } //if-else get-data } else { - bool is_active; - JSONDecoder::decode_json("active", is_active, &parser); - if (! is_active) { - ldpp_dout(dpp, 0) << "Active state is false" << dendl; - throw -ERR_INVALID_IDENTITY_TOKEN; - } - JSONDecoder::decode_json("iss", token.iss, &parser); - JSONDecoder::decode_json("aud", token.aud, &parser); - JSONDecoder::decode_json("sub", token.sub, &parser); - JSONDecoder::decode_json("user_name", token.user_name, &parser); - } - return token; + ldpp_dout(dpp, 0) << "Malformed json returned while fetching cert" << dendl; + throw -EINVAL; + } //if-else parser cert_resp + } else { + ldpp_dout(dpp, 0) << "JWT signed by HMAC algos are currently not supported" << dendl; + throw -EINVAL; } - return boost::none; } WebTokenEngine::result_t @@ -104,8 +223,9 @@ WebTokenEngine::authenticate( const DoutPrefixProvider* dpp, } try { - t = get_from_idp(dpp, token); - } catch(...) { + t = get_from_jwt(dpp, token); + } + catch (...) { return result_t::deny(-EACCES); } @@ -116,7 +236,7 @@ WebTokenEngine::authenticate( const DoutPrefixProvider* dpp, return result_t::deny(-EACCES); } -} // namespace rgw::auth::s3 +} // namespace rgw::auth::sts int RGWREST_STS::verify_permission() { diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index bac127348eb44..c0f36b473146b 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -7,6 +7,7 @@ #include "rgw_auth_filters.h" #include "rgw_sts.h" #include "rgw_web_idp.h" +#include "jwt-cpp/jwt.h" namespace rgw::auth::sts { @@ -21,8 +22,10 @@ class WebTokenEngine : public rgw::auth::Engine { bool is_applicable(const std::string& token) const noexcept; - boost::optional - get_from_idp(const DoutPrefixProvider* dpp, const std::string& token) const; + boost::optional + get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token) const; + + void validate_signature (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss) const; result_t authenticate(const DoutPrefixProvider* dpp, const std::string& token, diff --git a/src/rgw/rgw_web_idp.h b/src/rgw/rgw_web_idp.h index 30bcebd804c10..f4af38b08a0af 100644 --- a/src/rgw/rgw_web_idp.h +++ b/src/rgw/rgw_web_idp.h @@ -16,7 +16,9 @@ struct WebTokenClaims { //Issuer of this token std::string iss; //Human-readable id for the resource owner - string user_name; + std::string user_name; + //Client Id + std::string client_id; }; }; /* namespace web_idp */ -- 2.39.5