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: v17.1.0~969^2~13 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=f05184bd73d5e123f2ff699a24ba9cf32ea7a668;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 --- diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index 697f2a29cde8b..b7d48d603f7ee 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -448,6 +448,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 971e8b393b834..fc84a79b5bbe3 100644 --- a/src/rgw/rgw_auth.h +++ b/src/rgw/rgw_auth.h @@ -375,6 +375,7 @@ protected: std::string role_session; std::string role_tenant; std::unordered_multimap token_claims; + boost::optional>> principal_tags; std::string get_idp_url() const; @@ -387,12 +388,14 @@ public: rgw::sal::Store* store, 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), store(store), 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; @@ -475,7 +478,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 f1768672165ed..3bacbaabda76e 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -1650,6 +1650,9 @@ struct req_state : DoutPrefixProvider { jspan trace; + //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 802e22b557f88..f91a349aee7c4 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -156,6 +156,7 @@ static const actpair actpairs[] = { "sts:AssumeRole", stsAssumeRole}, { "sts:AssumeRoleWithWebIdentity", stsAssumeRoleWithWebIdentity}, { "sts:GetSessionToken", stsGetSessionToken}, + { "sts:TagSession", stsTagSession}, }; struct PolicyParser; @@ -1312,6 +1313,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 03b690882bcf0..84dce04cf2472 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -133,7 +133,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 e2e8dd1d00bd0..44810d7126568 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -134,12 +134,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: @@ -147,20 +148,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; } @@ -168,7 +169,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; } @@ -177,24 +178,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); @@ -224,12 +229,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()) { @@ -254,20 +272,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}; } std::string @@ -440,31 +458,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 @@ -487,16 +502,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 f7b95fe5e5ebc..576e1f859669e 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; rgw::sal::Store* store; 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; @@ -35,7 +38,7 @@ class WebTokenEngine : public rgw::auth::Engine { std::string get_cert_url(const std::string& iss, const DoutPrefixProvider *dpp,optional_yield y) 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 std::string& algorithm, const std::string& iss, const std::vector& thumbprints, optional_yield y) const; @@ -44,8 +47,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, @@ -87,9 +91,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, store, s, - rgw::auth::WebIdentityApplier(cct, store, role_session, role_tenant, token)); + rgw::auth::WebIdentityApplier(cct, store, 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 f4af38b08a0af..089c4da665dc2 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 */