From: Pritha Srivastava Date: Thu, 4 Mar 2021 08:57:46 +0000 (+0530) Subject: rgw/sts: adding code for aws:RequestTags as part X-Git-Tag: v16.2.11~244^2~13 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=7f512db7ee2093ffd5a2723cec1ffcfe2b1761ed;p=ceph.git rgw/sts: adding code for aws:RequestTags as part of session tags implementation. Session Tags can be passed in the web token in AssumeRoleWithWebIdentity call by configuring them in the IDP. These tags can be used as Conditions in the trust policy of a role as aws:RequestTag, based on which a federated user is allowed to assume a role. The trust policy should have a statement for 'sts:TagSession' alongwith 'sts:AssumeRoleWithWebIdentity' in case principal tags are passed in the web token. Signed-off-by: Pritha Srivastava (cherry picked from commit f05184bd73d5e123f2ff699a24ba9cf32ea7a668) --- diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index 10139dadaaa..e5492c36425 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -445,6 +445,37 @@ void rgw::auth::WebIdentityApplier::modify_request_state(const DoutPrefixProvide condition = idp_url + ":" + claim.first; s->env.emplace(condition, claim.second); } + + if (principal_tags) { + constexpr size_t KEY_SIZE = 128, VAL_SIZE = 256; + std::set> p_tags = principal_tags.get(); + for (auto& it : p_tags) { + string key = it.first; + string val = it.second; + if (key.find("aws:") == 0 || val.find("aws:") == 0) { + ldpp_dout(dpp, 0) << "ERROR: Tag/Value can't start with aws:, hence skipping it" << dendl; + continue; + } + if (key.size() > KEY_SIZE || val.size() > VAL_SIZE) { + ldpp_dout(dpp, 0) << "ERROR: Invalid tag/value size, hence skipping it" << dendl; + continue; + } + std::string p_key = "aws:PrincipalTag/"; + p_key.append(key); + s->principal_tags.emplace_back(std::make_pair(p_key, val)); + ldpp_dout(dpp, 10) << "Principal Tag Key: " << p_key << " Value: " << val << dendl; + + std::string e_key = "aws:RequestTag/"; + e_key.append(key); + s->env.emplace(e_key, val); + ldpp_dout(dpp, 10) << "RGW Env Tag Key: " << e_key << " Value: " << val << dendl; + + if (s->principal_tags.size() == 50) { + ldpp_dout(dpp, 0) << "ERROR: Number of tag/value pairs exceeding 50, hence skipping the rest" << dendl; + break; + } + } + } } 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 e14a5ab2d6e..1614259815e 100644 --- a/src/rgw/rgw_auth.h +++ b/src/rgw/rgw_auth.h @@ -379,6 +379,7 @@ protected: std::string role_session; std::string role_tenant; std::unordered_multimap token_claims; + boost::optional>> principal_tags; string get_idp_url() const; @@ -391,12 +392,14 @@ public: RGWCtl* const ctl, const std::string& role_session, const std::string& role_tenant, - const std::unordered_multimap& token_claims) + const std::unordered_multimap& token_claims, + boost::optional>> principal_tags) : cct(cct), ctl(ctl), role_session(role_session), role_tenant(role_tenant), - token_claims(token_claims) { + token_claims(token_claims), + principal_tags(principal_tags) { const auto& sub = token_claims.find("sub"); if(sub != token_claims.end()) { this->sub = sub->second; @@ -479,7 +482,8 @@ public: const req_state* s, const std::string& role_session, const std::string& role_tenant, - const std::unordered_multimap& token) const = 0; + const std::unordered_multimap& token, + boost::optional>> principal_tags) const = 0; }; }; diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 50e4ddb968f..a068f145596 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -1624,6 +1624,9 @@ struct req_state : DoutPrefixProvider { vector session_policies; + //Principal tags that come in as part of AssumeRoleWithWebIdentity + std::vector> principal_tags; + req_state(CephContext* _cct, RGWEnv* e, uint64_t id); ~req_state(); diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index acf2d0046b1..f373522d941 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -152,6 +152,7 @@ static const actpair actpairs[] = { "sts:AssumeRole", stsAssumeRole}, { "sts:AssumeRoleWithWebIdentity", stsAssumeRoleWithWebIdentity}, { "sts:GetSessionToken", stsGetSessionToken}, + { "sts:TagSession", stsTagSession}, }; struct PolicyParser; @@ -1302,6 +1303,9 @@ const char* action_bit_string(uint64_t action) { case stsGetSessionToken: return "sts:GetSessionToken"; + + case stsTagSession: + return "sts:TagSession"; } return "s3Invalid"; } diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index f0e542429a5..29e40486fcb 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -131,7 +131,8 @@ static constexpr std::uint64_t iamAll = s3All + 18; static constexpr std::uint64_t stsAssumeRole = iamAll + 1; static constexpr std::uint64_t stsAssumeRoleWithWebIdentity = iamAll + 2; static constexpr std::uint64_t stsGetSessionToken = iamAll + 3; -static constexpr std::uint64_t stsAll = iamAll + 4; +static constexpr std::uint64_t stsTagSession = iamAll + 4; +static constexpr std::uint64_t stsAll = iamAll + 5; static constexpr std::uint64_t s3Count = s3All; static constexpr std::uint64_t allCount = stsAll + 1; diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index be3b0008512..e84e7271419 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -130,12 +130,13 @@ WebTokenEngine::is_cert_valid(const vector& thumbprints, const string& c return false; } +template void -WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std::unordered_multimap& token) const +WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, T& t) const { string s_val; - jwt::claim::type t = c.get_type(); - switch(t) { + jwt::claim::type c_type = c.get_type(); + switch(c_type) { case jwt::claim::type::null: break; case jwt::claim::type::boolean: @@ -143,20 +144,20 @@ WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std:: case jwt::claim::type::int64: { s_val = c.to_json().serialize(); - token.emplace(key, s_val); + t.emplace(std::make_pair(key, s_val)); break; } case jwt::claim::type::string: { s_val = c.to_json().to_str(); - token.emplace(key, s_val); + t.emplace(std::make_pair(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); + recurse_and_insert(key, jwt::claim(a), t); } break; } @@ -164,7 +165,7 @@ WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std:: { const picojson::object& obj = c.as_object(); for (auto& m : obj) { - recurse_and_insert(m.first, jwt::claim(m.second), token); + recurse_and_insert(m.first, jwt::claim(m.second), t); } break; } @@ -173,24 +174,28 @@ WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std:: } //Extract all token claims so that they can be later used in the Condition element of Role's trust policy -std::unordered_multimap +WebTokenEngine::token_t WebTokenEngine::get_token_claims(const jwt::decoded_jwt& decoded) const { - std::unordered_multimap token; + WebTokenEngine::token_t token; const auto& claims = decoded.get_payload_claims(); for (auto& c : claims) { + if (c.first == string(princTagsNamespace)) { + continue; + } 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 +std::tuple, boost::optional> WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s, optional_yield y) const { WebTokenEngine::token_t t; + WebTokenEngine::principal_tags_t principal_tags; try { const auto& decoded = jwt::decode(token); @@ -220,12 +225,25 @@ WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& t 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, iss); if (! provider) { ldpp_dout(dpp, 0) << "Couldn't get oidc provider info using input iss" << iss << dendl; throw -EACCES; } + if (decoded.has_payload_claim(string(princTagsNamespace))) { + auto& cl = decoded.get_payload_claim(string(princTagsNamespace)); + if (cl.get_type() == jwt::claim::type::object || cl.get_type() == jwt::claim::type::array) { + recurse_and_insert("dummy", cl, principal_tags); + for (auto it : principal_tags) { + ldpp_dout(dpp, 5) << "Key: " << it.first << " Value: " << it.second << dendl; + } + } else { + ldpp_dout(dpp, 0) << "Malformed principal tags" << cl.as_string() << dendl; + throw -EINVAL; + } + } vector client_ids = provider->get_client_ids(); vector thumbprints = provider->get_thumbprints(); if (! client_ids.empty()) { @@ -250,20 +268,20 @@ WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& t throw -EACCES; } } else { - return boost::none; + return {boost::none, boost::none}; } } catch (int error) { if (error == -EACCES) { throw -EACCES; } ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl; - return boost::none; + return {boost::none, boost::none}; } catch (...) { ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl; - return boost::none; + return {boost::none, boost::none}; } - return t; + return {t, principal_tags}; } void @@ -398,31 +416,28 @@ WebTokenEngine::authenticate( const DoutPrefixProvider* dpp, const req_state* const s, optional_yield y) const { - boost::optional t; - if (! is_applicable(token)) { return result_t::deny(); } try { - t = get_from_jwt(dpp, token, s, y); + auto [t, princ_tags] = get_from_jwt(dpp, token, s, y); + if (t) { + string role_session = s->info.args.get("RoleSessionName"); + if (role_session.empty()) { + ldout(s->cct, 0) << "Role Session Name is empty " << dendl; + return result_t::deny(-EACCES); + } + string role_arn = s->info.args.get("RoleArn"); + string role_tenant = get_role_tenant(role_arn); + auto apl = apl_factory->create_apl_web_identity(cct, s, role_session, role_tenant, *t, princ_tags); + return result_t::grant(std::move(apl)); + } + return result_t::deny(-EACCES); } catch (...) { return result_t::deny(-EACCES); } - - if (t) { - string role_session = s->info.args.get("RoleSessionName"); - if (role_session.empty()) { - ldpp_dout(dpp, 0) << "Role Session Name is empty " << dendl; - return result_t::deny(-EACCES); - } - string role_arn = s->info.args.get("RoleArn"); - string role_tenant = get_role_tenant(role_arn); - auto apl = apl_factory->create_apl_web_identity(cct, s, role_session, role_tenant, *t); - return result_t::grant(std::move(apl)); - } - return result_t::deny(-EACCES); } } // namespace rgw::auth::sts @@ -445,16 +460,23 @@ int RGWREST_STS::verify_permission(optional_yield y) //TODO - This step should be part of Role Creation try { const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl); - //Check if the input role arn is there as one of the Principals in the policy, - // If yes, then return 0, else -EPERM - auto p_res = p.eval_principal(s->env, *s->auth.identity); - if (p_res == rgw::IAM::Effect::Deny) { - ldpp_dout(this, 0) << "evaluating principal returned deny" << dendl; - return -EPERM; + if (!s->principal_tags.empty()) { + auto res = p.eval(s->env, *s->auth.identity, rgw::IAM::stsTagSession, rgw::ARN()); + if (res != rgw::IAM::Effect::Allow) { + ldout(s->cct, 0) << "evaluating policy for stsTagSession returned deny/pass" << dendl; + return -EPERM; + } } - auto c_res = p.eval_conditions(s->env); - if (c_res == rgw::IAM::Effect::Deny) { - ldpp_dout(this, 0) << "evaluating condition returned deny" << dendl; + uint64_t op; + if (get_type() == RGW_STS_ASSUME_ROLE_WEB_IDENTITY) { + op = rgw::IAM::stsAssumeRoleWithWebIdentity; + } else { + op = rgw::IAM::stsAssumeRole; + } + + auto res = p.eval(s->env, *s->auth.identity, op, rgw::ARN()); + if (res != rgw::IAM::Effect::Allow) { + ldout(s->cct, 0) << "evaluating policy for op: " << op << " returned deny/pass" << dendl; return -EPERM; } } catch (rgw::IAM::PolicyParseException& e) { diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index f3c38389125..dc057c86163 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -14,11 +14,14 @@ namespace rgw::auth::sts { class WebTokenEngine : public rgw::auth::Engine { + static constexpr std::string_view princTagsNamespace = "https://aws.amazon.com/tags"; CephContext* const cct; RGWCtl* const ctl; using result_t = rgw::auth::Engine::result_t; - using token_t = std::unordered_multimap; + using Pair = std::pair; + using token_t = std::unordered_multimap; + using principal_tags_t = std::set; const rgw::auth::TokenExtractor* const extractor; const rgw::auth::WebIdentityApplier::Factory* const apl_factory; @@ -33,7 +36,7 @@ class WebTokenEngine : public rgw::auth::Engine { std::string get_role_tenant(const string& role_arn) const; - boost::optional + 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 (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector& thumbprints, optional_yield y) const; @@ -42,8 +45,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; + template + void recurse_and_insert(const string& key, const jwt::claim& c, T& t) const; + WebTokenEngine::token_t get_token_claims(const jwt::decoded_jwt& decoded) const; public: WebTokenEngine(CephContext* const cct, @@ -85,9 +89,10 @@ class DefaultStrategy : public rgw::auth::Strategy, const req_state* s, const std::string& role_session, const std::string& role_tenant, - const std::unordered_multimap& token) const override { + const std::unordered_multimap& token, + boost::optional>> principal_tags) const override { auto apl = rgw::auth::add_sysreq(cct, ctl, s, - rgw::auth::WebIdentityApplier(cct, ctl, role_session, role_tenant, token)); + rgw::auth::WebIdentityApplier(cct, ctl, role_session, role_tenant, token, principal_tags)); return aplptr_t(new decltype(apl)(std::move(apl))); } diff --git a/src/rgw/rgw_web_idp.h b/src/rgw/rgw_web_idp.h index f4af38b08a0..089c4da665d 100644 --- a/src/rgw/rgw_web_idp.h +++ b/src/rgw/rgw_web_idp.h @@ -19,6 +19,8 @@ struct WebTokenClaims { std::string user_name; //Client Id std::string client_id; + //azp + std::string azp; }; }; /* namespace web_idp */