]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/sts: adding code to enable usage of all
authorPritha Srivastava <prsrivas@redhat.com>
Wed, 31 Mar 2021 08:57:20 +0000 (14:27 +0530)
committerPritha Srivastava <prsrivas@redhat.com>
Wed, 1 Sep 2021 10:26:17 +0000 (15:56 +0530)
token claims as 'Condition' of a role's
trust policy.

Signed-off-by: Pritha Srivastava <prsrivas@redhat.com>
src/rgw/jwt-cpp/jwt.h
src/rgw/rgw_auth.cc
src/rgw/rgw_auth.h
src/rgw/rgw_iam_policy.cc
src/rgw/rgw_iam_policy.h
src/rgw/rgw_op.cc
src/rgw/rgw_rest_sts.cc
src/rgw/rgw_rest_sts.h
src/test/rgw/test_rgw_iam_policy.cc
src/test/rgw/test_rgw_lua.cc

index 3d843861d654faa49bbf200b3aaad2a7b7c7a251..b86fb57b032f677592b539810dd88333cb38435b 100644 (file)
@@ -906,6 +906,16 @@ namespace jwt {
                                throw std::bad_cast();
                        return val.get<double>();
                }
+               /**
+                * 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<picojson::object>())
+                               throw std::bad_cast();
+                       return val.get<picojson::object>();
+               }
        };
 
        /**
index daf4535d1ad0ad7fb508cb81ba2b8b90d76df692..697f2a29cde8b5a0c3ebf0a744f62a02bf363e51 100644 (file)
@@ -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
index 92c3a20b69beee990ad5edf4428e51ed294204f7..971e8b393b83478ca631201f1c51c99bef01b7c7 100644 (file)
@@ -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<std::string, std::string> 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<std::string, std::string>& 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<std::string, std::string>& token) const = 0;
   };
 };
 
index b14ce57ab0799b450722dad5404f93cbbfe125a5..802e22b557f88a4c973d2786ab5528f463fe6fb2 100644 (file)
@@ -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<std::string>(), s, vals);
+    return orrible(std::equal_to<std::string>(), itr, vals);
 
   case TokenID::StringNotEquals:
     return orrible(std::not_fn(std::equal_to<std::string>()),
-                  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:
index 4e1adc5a1f15587ab88e1f8a06e7cb2f4f102b78..03b690882bcf0b126f243e424be7d93677c00cbf 100644 (file)
@@ -252,7 +252,7 @@ enum class PolicyPrincipal {
   Other
 };
 
-using Environment = boost::container::flat_map<std::string, std::string>;
+using Environment = std::unordered_multimap<std::string, std::string>;
 
 using Address = std::bitset<128>;
 struct MaskedIP {
@@ -389,13 +389,16 @@ struct Condition {
     }
   };
 
+  using unordered_multimap_it_pair = std::pair <std::unordered_multimap<std::string,std::string>::const_iterator, std::unordered_multimap<std::string,std::string>::const_iterator>;
   template<typename F>
-  static bool orrible(F&& f, const std::string& c,
+  static bool orrible(F&& f, const unordered_multimap_it_pair& it,
                      const std::vector<std::string>& v) {
-    for (const auto& d : v) {
-      if (std::forward<F>(f)(c, d)) {
-       return true;
+    for (auto itr = it.first; itr != it.second; itr++) {
+      for (const auto& d : v) {
+        if (std::forward<F>(f)(itr->second, d)) {
+               return true;
       }
+     }
     }
     return false;
   }
index d6b1f249fa249fa928d709aa5556d7ce171c9f78..b3f2f85c2dc8c56954587cfd41ae5fc78c3636a8 100644 (file)
@@ -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);
       }
     }
   }
index 561bb4322fd531c0565d0a134fc425a32c559fff..e2e8dd1d00bd04cc807cf99ebb0b90dfb6dcac79 100644 (file)
@@ -134,6 +134,61 @@ WebTokenEngine::is_cert_valid(const vector<string>& thumbprints, const string& c
   return false;
 }
 
+void
+WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, std::unordered_multimap<string, string>& 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<string,string>
+WebTokenEngine::get_token_claims(const jwt::decoded_jwt& decoded) const
+{
+  std::unordered_multimap<string, string> 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::token_t>
 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<string> 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<string> client_ids = provider->get_client_ids();
     vector<string> 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;
       }
index 741072fe82998800609fbcc41c07c9f9be336429..f7b95fe5e5ebc4624efa63b4854ba4ef197c9319 100644 (file)
@@ -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<string,string>;
 
   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<string, string>& token) const;
+  std::unordered_multimap<string,string> 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<string, string>& 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)));
index 1b450af1c62e5b819ddb292451d0bc1ae3fab17d..600f152258c04cee216feee48c9a47cb27175efd 100644 (file)
@@ -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"));
index f9459e1146e12f4e2ae417e30983373843f289ba..bf70e6fbd821f36fe6a7426da29800536d1fb818 100644 (file)
@@ -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);