From: Pritha Srivastava Date: Wed, 31 Mar 2021 08:57:20 +0000 (+0530) Subject: rgw/sts: adding code to enable usage of all X-Git-Tag: v17.1.0~969^2~14 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=78f13d6de19f0260fe47d1d8e97b5e8f47bf1184;p=ceph.git rgw/sts: adding code to enable usage of all token claims as 'Condition' of a role's trust policy. Signed-off-by: Pritha Srivastava --- diff --git a/src/rgw/jwt-cpp/jwt.h b/src/rgw/jwt-cpp/jwt.h index 3d843861d654..b86fb57b032f 100644 --- a/src/rgw/jwt-cpp/jwt.h +++ b/src/rgw/jwt-cpp/jwt.h @@ -906,6 +906,16 @@ namespace jwt { throw std::bad_cast(); return val.get(); } + /** + * Get the contained object as an object + * \return content as object + * \throws std::bad_cast Content was not an object + */ + const picojson::object& as_object() const { + if (!val.is()) + throw std::bad_cast(); + return val.get(); + } }; /** diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index daf4535d1ad0..697f2a29cde8 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -351,15 +351,14 @@ rgw::auth::Strategy::add_engine(const Control ctrl_flag, void rgw::auth::WebIdentityApplier::to_str(std::ostream& out) const { - out << "rgw::auth::WebIdentityApplier(sub =" << token_claims.sub - << ", user_name=" << token_claims.user_name - << ", aud =" << token_claims.aud - << ", provider_id =" << token_claims.iss << ")"; + out << "rgw::auth::WebIdentityApplier(sub =" << sub + << ", user_name=" << user_name + << ", provider_id =" << iss << ")"; } string rgw::auth::WebIdentityApplier::get_idp_url() const { - string idp_url = token_claims.iss; + string idp_url = this->iss; idp_url = url_remove_prefix(idp_url); return idp_url; } @@ -388,7 +387,7 @@ void rgw::auth::WebIdentityApplier::create_account(const DoutPrefixProvider* dpp void rgw::auth::WebIdentityApplier::load_acct_info(const DoutPrefixProvider* dpp, RGWUserInfo& user_info) const { rgw_user federated_user; - federated_user.id = token_claims.sub; + federated_user.id = this->sub; federated_user.tenant = role_tenant; federated_user.ns = "oidc"; @@ -427,24 +426,28 @@ void rgw::auth::WebIdentityApplier::load_acct_info(const DoutPrefixProvider* dpp } ldpp_dout(dpp, 0) << "NOTICE: couldn't map oidc federated user " << federated_user << dendl; - create_account(dpp, federated_user, token_claims.user_name, user_info); + create_account(dpp, federated_user, this->user_name, user_info); } void rgw::auth::WebIdentityApplier::modify_request_state(const DoutPrefixProvider *dpp, req_state* s) const { - 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); + s->info.args.append("sub", this->sub); + s->info.args.append("aud", this->aud); + s->info.args.append("provider_id", this->iss); + s->info.args.append("client_id", this->client_id); + string condition; string idp_url = get_idp_url(); - string condition = idp_url + ":app_id"; - - s->env.emplace(condition, token_claims.aud); - - condition.clear(); - condition = idp_url + ":sub"; - s->env.emplace(condition, token_claims.sub); + for (auto& claim : token_claims) { + if (claim.first == "aud") { + condition.clear(); + condition = idp_url + ":app_id"; + s->env.emplace(condition, claim.second); + } + condition.clear(); + condition = idp_url + ":" + claim.first; + s->env.emplace(condition, claim.second); + } } bool rgw::auth::WebIdentityApplier::is_identity(const idset_t& ids) const diff --git a/src/rgw/rgw_auth.h b/src/rgw/rgw_auth.h index 92c3a20b69be..971e8b393b83 100644 --- a/src/rgw/rgw_auth.h +++ b/src/rgw/rgw_auth.h @@ -364,12 +364,17 @@ protected: class StrategyRegistry; class WebIdentityApplier : public IdentityApplier { + std::string sub; + std::string iss; + std::string aud; + std::string client_id; + std::string user_name; protected: CephContext* const cct; rgw::sal::Store* store; std::string role_session; std::string role_tenant; - rgw::web_idp::WebTokenClaims token_claims; + std::unordered_multimap token_claims; std::string get_idp_url() const; @@ -382,12 +387,46 @@ public: rgw::sal::Store* store, const std::string& role_session, const std::string& role_tenant, - const rgw::web_idp::WebTokenClaims& token_claims) - : cct(cct), + const std::unordered_multimap& token_claims) + : cct(cct), store(store), role_session(role_session), role_tenant(role_tenant), token_claims(token_claims) { + const auto& sub = token_claims.find("sub"); + if(sub != token_claims.end()) { + this->sub = sub->second; + } + + const auto& iss = token_claims.find("iss"); + if(iss != token_claims.end()) { + this->iss = iss->second; + } + + const auto& aud = token_claims.find("aud"); + if(aud != token_claims.end()) { + this->aud = aud->second; + } + + const auto& client_id = token_claims.find("client_id"); + if(client_id != token_claims.end()) { + this->client_id = client_id->second; + } else { + const auto& azp = token_claims.find("azp"); + if (azp != token_claims.end()) { + this->client_id = azp->second; + } + } + + const auto& user_name = token_claims.find("username"); + if(user_name != token_claims.end()) { + this->user_name = user_name->second; + } else { + const auto& given_username = token_claims.find("given_username"); + if (given_username != token_claims.end()) { + this->user_name = given_username->second; + } + } } void modify_request_state(const DoutPrefixProvider *dpp, req_state* s) const override; @@ -401,7 +440,7 @@ public: } bool is_owner_of(const rgw_user& uid) const override { - if (uid.id == token_claims.sub && uid.tenant == role_tenant && uid.ns == "oidc") { + if (uid.id == this->sub && uid.tenant == role_tenant && uid.ns == "oidc") { return true; } return false; @@ -422,7 +461,7 @@ public: } std::string get_acct_name() const override { - return token_claims.user_name; + return this->user_name; } std::string get_subuser() const override { @@ -436,7 +475,7 @@ public: const req_state* s, const std::string& role_session, const std::string& role_tenant, - const rgw::web_idp::WebTokenClaims& token) const = 0; + const std::unordered_multimap& token) const = 0; }; }; diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index b14ce57ab079..802e22b557f8 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -685,26 +685,28 @@ bool Condition::eval(const Environment& env) const { } const auto& s = i->second; + const auto& itr = env.equal_range(key); + switch (op) { // String! case TokenID::StringEquals: - return orrible(std::equal_to(), s, vals); + return orrible(std::equal_to(), itr, vals); case TokenID::StringNotEquals: return orrible(std::not_fn(std::equal_to()), - s, vals); + itr, vals); case TokenID::StringEqualsIgnoreCase: - return orrible(ci_equal_to(), s, vals); + return orrible(ci_equal_to(), itr, vals); case TokenID::StringNotEqualsIgnoreCase: - return orrible(std::not_fn(ci_equal_to()), s, vals); + return orrible(std::not_fn(ci_equal_to()), itr, vals); case TokenID::StringLike: - return orrible(string_like(), s, vals); + return orrible(string_like(), itr, vals); case TokenID::StringNotLike: - return orrible(std::not_fn(string_like()), s, vals); + return orrible(std::not_fn(string_like()), itr, vals); // Numeric case TokenID::NumericEquals: diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index 4e1adc5a1f15..03b690882bcf 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -252,7 +252,7 @@ enum class PolicyPrincipal { Other }; -using Environment = boost::container::flat_map; +using Environment = std::unordered_multimap; using Address = std::bitset<128>; struct MaskedIP { @@ -389,13 +389,16 @@ struct Condition { } }; + using unordered_multimap_it_pair = std::pair ::const_iterator, std::unordered_multimap::const_iterator>; template - static bool orrible(F&& f, const std::string& c, + static bool orrible(F&& f, const unordered_multimap_it_pair& it, const std::vector& v) { - for (const auto& d : v) { - if (std::forward(f)(c, d)) { - return true; + for (auto itr = it.first; itr != it.second; itr++) { + for (const auto& d : v) { + if (std::forward(f)(itr->second, d)) { + return true; } + } } return false; } diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index d6b1f249fa24..b3f2f85c2dc8 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -725,7 +725,7 @@ static void rgw_add_grant_to_iam_environment(rgw::IAM::Environment& e, struct re for (const auto& c: acl_header_conditionals){ auto hdr = s->info.env->get(c.first); if(hdr) { - e[c.second] = hdr; + e.emplace(c.second, hdr); } } } diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index 561bb4322fd5..e2e8dd1d00bd 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -134,6 +134,61 @@ WebTokenEngine::is_cert_valid(const vector& thumbprints, const string& c return false; } +void +WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std::unordered_multimap& token) const +{ + string s_val; + jwt::claim::type t = c.get_type(); + switch(t) { + case jwt::claim::type::null: + break; + case jwt::claim::type::boolean: + case jwt::claim::type::number: + case jwt::claim::type::int64: + { + s_val = c.to_json().serialize(); + token.emplace(key, s_val); + break; + } + case jwt::claim::type::string: + { + s_val = c.to_json().to_str(); + token.emplace(key, s_val); + break; + } + case jwt::claim::type::array: + { + const picojson::array& arr = c.as_array(); + for (auto& a : arr) { + recurse_and_insert(key, jwt::claim(a), token); + } + break; + } + case jwt::claim::type::object: + { + const picojson::object& obj = c.as_object(); + for (auto& m : obj) { + recurse_and_insert(m.first, jwt::claim(m.second), token); + } + break; + } + } + return; +} + +//Extract all token claims so that they can be later used in the Condition element of Role's trust policy +std::unordered_multimap +WebTokenEngine::get_token_claims(const jwt::decoded_jwt& decoded) const +{ + std::unordered_multimap token; + const auto& claims = decoded.get_payload_claims(); + + for (auto& c : claims) { + recurse_and_insert(c.first, c.second, token); + } + return token; +} + //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 req_state* const s, @@ -145,32 +200,47 @@ WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& t auto& payload = decoded.get_payload(); ldpp_dout(dpp, 20) << " payload = " << payload << dendl; + + t = get_token_claims(decoded); + + string iss; if (decoded.has_issuer()) { - t.iss = decoded.get_issuer(); + iss = decoded.get_issuer(); } + + set aud; if (decoded.has_audience()) { - auto aud = decoded.get_audience(); - t.aud = *(aud.begin()); - } - if (decoded.has_subject()) { - t.sub = decoded.get_subject(); + aud = decoded.get_audience(); } + + string client_id; if (decoded.has_payload_claim("client_id")) { - t.client_id = decoded.get_payload_claim("client_id").as_string(); + 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(); + if (client_id.empty() && decoded.has_payload_claim("clientId")) { + client_id = decoded.get_payload_claim("clientId").as_string(); + } + string azp; + if (decoded.has_payload_claim("azp")) { + azp = decoded.get_payload_claim("azp").as_string(); } string role_arn = s->info.args.get("RoleArn"); - auto provider = get_provider(dpp, role_arn, t.iss); + auto provider = get_provider(dpp, role_arn, iss); if (! provider) { - ldpp_dout(dpp, 0) << "Couldn't get oidc provider info using input iss" << t.iss << dendl; + ldpp_dout(dpp, 0) << "Couldn't get oidc provider info using input iss" << iss << dendl; 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)) { + bool found = false; + for (auto& it : aud) { + if (is_client_id_valid(client_ids, it)) { + found = true; + break; + } + } + if (! found && ! is_client_id_valid(client_ids, client_id) && ! is_client_id_valid(client_ids, azp)) { ldpp_dout(dpp, 0) << "Client id in token doesn't match with that registered with oidc provider" << dendl; throw -EACCES; } @@ -179,7 +249,7 @@ WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& t if (decoded.has_algorithm()) { auto& algorithm = decoded.get_algorithm(); try { - validate_signature(dpp, decoded, algorithm, t.iss, thumbprints, y); + validate_signature(dpp, decoded, algorithm, iss, thumbprints, y); } catch (...) { throw -EACCES; } diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index 741072fe8299..f7b95fe5e5eb 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -18,7 +18,7 @@ class WebTokenEngine : public rgw::auth::Engine { rgw::sal::Store* store; using result_t = rgw::auth::Engine::result_t; - using token_t = rgw::web_idp::WebTokenClaims; + using token_t = std::unordered_multimap; const rgw::auth::TokenExtractor* const extractor; const rgw::auth::WebIdentityApplier::Factory* const apl_factory; @@ -44,6 +44,9 @@ class WebTokenEngine : public rgw::auth::Engine { const std::string& token, const req_state* s, optional_yield y) const; + void recurse_and_insert(const string& key, const jwt::claim& c, std::unordered_multimap& token) const; + std::unordered_multimap get_token_claims(const jwt::decoded_jwt& decoded) const; + public: WebTokenEngine(CephContext* const cct, rgw::sal::Store* store, @@ -84,7 +87,7 @@ class DefaultStrategy : public rgw::auth::Strategy, const req_state* s, const std::string& role_session, const std::string& role_tenant, - const rgw::web_idp::WebTokenClaims& token) const override { + const std::unordered_multimap& token) const override { auto apl = rgw::auth::add_sysreq(cct, store, s, rgw::auth::WebIdentityApplier(cct, store, role_session, role_tenant, token)); return aplptr_t(new decltype(apl)(std::move(apl))); diff --git a/src/test/rgw/test_rgw_iam_policy.cc b/src/test/rgw/test_rgw_iam_policy.cc index 1b450af1c62e..600f152258c0 100644 --- a/src/test/rgw/test_rgw_iam_policy.cc +++ b/src/test/rgw/test_rgw_iam_policy.cc @@ -1021,10 +1021,10 @@ TEST_F(IPPolicyTest, EvalIPAddress) { bufferlist::static_from_string(ip_address_full_example)); Environment e; Environment allowedIP, blocklistedIP, allowedIPv6, blocklistedIPv6; - allowedIP["aws:SourceIp"] = "192.168.1.2"; - allowedIPv6["aws:SourceIp"] = "::1"; - blocklistedIP["aws:SourceIp"] = "192.168.1.1"; - blocklistedIPv6["aws:SourceIp"] = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"; + allowedIP.emplace("aws:SourceIp","192.168.1.2"); + allowedIPv6.emplace("aws:SourceIp", "::1"); + blocklistedIP.emplace("aws:SourceIp", "192.168.1.1"); + blocklistedIPv6.emplace("aws:SourceIp", "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); auto trueacct = FakeIdentity( Principal::tenant("ACCOUNT-ID-WITHOUT-HYPHENS")); diff --git a/src/test/rgw/test_rgw_lua.cc b/src/test/rgw/test_rgw_lua.cc index f9459e1146e1..bf70e6fbd821 100644 --- a/src/test/rgw/test_rgw_lua.cc +++ b/src/test/rgw/test_rgw_lua.cc @@ -375,10 +375,9 @@ TEST(TestRGWLua, Environment) )"; DEFINE_REQ_STATE; - s.env[""] = "world"; - s.env[""] = "bar"; - s.env["goodbye"] = "cruel world"; - s.env["ka"] = "boom"; + s.env.emplace("", "bar"); + s.env.emplace("goodbye", "cruel world"); + s.env.emplace("ka", "boom"); const auto rc = lua::request::execute(nullptr, nullptr, nullptr, &s, "put_obj", script); ASSERT_EQ(rc, 0);