]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/sts: adding code for aws:RequestTags as part
authorPritha Srivastava <prsrivas@redhat.com>
Thu, 4 Mar 2021 08:57:46 +0000 (14:27 +0530)
committerPritha Srivastava <prsrivas@redhat.com>
Thu, 18 Aug 2022 07:40:36 +0000 (13:10 +0530)
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 <prsrivas@redhat.com>
(cherry picked from commit f05184bd73d5e123f2ff699a24ba9cf32ea7a668)

src/rgw/rgw_auth.cc
src/rgw/rgw_auth.h
src/rgw/rgw_common.h
src/rgw/rgw_iam_policy.cc
src/rgw/rgw_iam_policy.h
src/rgw/rgw_rest_sts.cc
src/rgw/rgw_rest_sts.h
src/rgw/rgw_web_idp.h

index 10139dadaaa2d624941af1e38397db22aef5cb5f..e5492c36425f72786f1f4d56f815b0fcae37d016 100644 (file)
@@ -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<std::pair<string, string>> 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
index e14a5ab2d6ef89001a97275791258641faf72a4f..1614259815e07966480e58918767607cb6a37cb4 100644 (file)
@@ -379,6 +379,7 @@ protected:
   std::string role_session;
   std::string role_tenant;
   std::unordered_multimap<std::string, std::string> token_claims;
+  boost::optional<std::set<std::pair<std::string, std::string>>> 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<std::string, std::string>& token_claims)
+                      const std::unordered_multimap<std::string, std::string>& token_claims,
+                      boost::optional<std::set<std::pair<std::string, std::string>>> 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<std::string, std::string>& token) const = 0;
+                                              const std::unordered_multimap<std::string, std::string>& token,
+                                              boost::optional<std::set<std::pair<std::string, std::string>>> principal_tags) const = 0;
   };
 };
 
index 50e4ddb968f742417bd5cbae5445afc43eeb3ddb..a068f145596718e59ebcf4a3f5c0c31baf42168e 100644 (file)
@@ -1624,6 +1624,9 @@ struct req_state : DoutPrefixProvider {
 
   vector<rgw::IAM::Policy> session_policies;
 
+  //Principal tags that come in as part of AssumeRoleWithWebIdentity
+  std::vector<std::pair<std::string, std::string>> principal_tags;
+
   req_state(CephContext* _cct, RGWEnv* e, uint64_t id);
   ~req_state();
 
index acf2d0046b108dd3bef6963ec5a20a5b6e50357b..f373522d941d469e1d89d99de8a7a36056989aaf 100644 (file)
@@ -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";
 }
index f0e542429a50710c97836aa943974259a98e9600..29e40486fcbe6d07f00e9ae0b82ffd9c53c108fb 100644 (file)
@@ -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;
index be3b00085124bf7e3ea1850c6a862e23df7e26eb..e84e7271419a6630a57f409a0c19526692d7dadb 100644 (file)
@@ -130,12 +130,13 @@ WebTokenEngine::is_cert_valid(const vector<string>& thumbprints, const string& c
   return false;
 }
 
+template <typename T>
 void
-WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std::unordered_multimap<string, string>& 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<string,string>
+WebTokenEngine::token_t
 WebTokenEngine::get_token_claims(const jwt::decoded_jwt& decoded) const
 {
-  std::unordered_multimap<string, string> 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<WebTokenEngine::token_t>
+std::tuple<boost::optional<WebTokenEngine::token_t>, boost::optional<WebTokenEngine::principal_tags_t>>
 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<string> client_ids = provider->get_client_ids();
     vector<string> 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<WebTokenEngine::token_t> 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) {
index f3c383891258820603512c0558c033595d18a32d..dc057c8616390a6003b4961011145d97d20a9069 100644 (file)
 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<string,string>;
+  using Pair = std::pair<std::string, std::string>;
+  using token_t = std::unordered_multimap<string, string>;
+  using principal_tags_t = std::set<Pair>;
 
   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<WebTokenEngine::token_t>
+  std::tuple<boost::optional<WebTokenEngine::token_t>, boost::optional<WebTokenEngine::principal_tags_t>>
   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<string>& 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<string, string>& token) const;
-  std::unordered_multimap<string,string> get_token_claims(const jwt::decoded_jwt& decoded) const;
+  template <typename T>
+  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<string, string>& token) const override {
+                                    const std::unordered_multimap<string, string>& token,
+                                    boost::optional<std::set<std::pair<std::string, std::string>>> 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)));
   }
 
index f4af38b08a0af2ae7827bf8121d9a0f502ae2e6d..089c4da665dc29cd89ea3219ccd388f86ddc3e97 100644 (file)
@@ -19,6 +19,8 @@ struct WebTokenClaims {
   std::string user_name;
   //Client Id
   std::string client_id;
+  //azp
+  std::string azp;
 };
 
 }; /* namespace web_idp */