]> git.apps.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>
Wed, 1 Sep 2021 10:26:17 +0000 (15:56 +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>
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 697f2a29cde8b5a0c3ebf0a744f62a02bf363e51..b7d48d603f7ee4bcded65560e210072ecd358c85 100644 (file)
@@ -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<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 971e8b393b83478ca631201f1c51c99bef01b7c7..fc84a79b5bbe3fed92ff487cc6bbe1acf379e907 100644 (file)
@@ -375,6 +375,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;
 
   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<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),
       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<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 f1768672165ede858fe5ec7339fc3b2cd01c9c56..3bacbaabda76e68cbbb8bf4956059f925c237c34 100644 (file)
@@ -1650,6 +1650,9 @@ struct req_state : DoutPrefixProvider {
 
   jspan trace;
 
+  //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 802e22b557f88a4c973d2786ab5528f463fe6fb2..f91a349aee7c4e1aa01fb4e290d0e68a8411eccc 100644 (file)
@@ -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";
 }
index 03b690882bcf0b126f243e424be7d93677c00cbf..84dce04cf24728771b3a0be5d8c3835d20c2f515 100644 (file)
@@ -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;
index e2e8dd1d00bd04cc807cf99ebb0b90dfb6dcac79..44810d71265680b15cf6f3bf392a3537b4b5a09a 100644 (file)
@@ -134,12 +134,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:
@@ -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<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);
 
@@ -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<string> client_ids = provider->get_client_ids();
     vector<string> 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<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
@@ -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) {
index f7b95fe5e5ebc4624efa63b4854ba4ef197c9319..576e1f859669e225baf2cb40a5446850b7112c0d 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;
   rgw::sal::Store* store;
 
   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;
@@ -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<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 std::string& algorithm, const std::string& iss, const std::vector<std::string>& 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<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,
@@ -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<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, 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)));
   }
 
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 */