]> 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>
Thu, 18 Aug 2022 07:01:18 +0000 (12:31 +0530)
token claims as 'Condition' of a role's
trust policy.

Signed-off-by: Pritha Srivastava <prsrivas@redhat.com>
(cherry picked from commit 78f13d6de19f0260fe47d1d8e97b5e8f47bf1184)

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 919be48ad7c24c28936ddd8781b7bfc39ec8b06d..10139dadaaa2d624941af1e38397db22aef5cb5f 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;
 }
@@ -389,7 +388,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";
 
@@ -424,24 +423,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 3fa02bd821fb7c7729aebd16db5169a9c8cbc87a..e14a5ab2d6ef89001a97275791258641faf72a4f 100644 (file)
@@ -368,12 +368,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;
   RGWCtl* const ctl;
-  string role_session;
-  string role_tenant;
-  rgw::web_idp::WebTokenClaims token_claims;
+  std::string role_session;
+  std::string role_tenant;
+  std::unordered_multimap<std::string, std::string> token_claims;
 
   string get_idp_url() const;
 
@@ -384,14 +389,48 @@ protected:
 public:
   WebIdentityApplier( CephContext* const cct,
                       RGWCtl* const ctl,
-                      const string& role_session,
-                      const string& role_tenant,
-                      const rgw::web_idp::WebTokenClaims& token_claims)
-    : cct(cct),
+                      const std::string& role_session,
+                      const std::string& role_tenant,
+                      const std::unordered_multimap<std::string, std::string>& token_claims)
+      : cct(cct),
       ctl(ctl),
       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;
@@ -405,7 +444,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;
@@ -425,8 +464,8 @@ public:
     return TYPE_WEB;
   }
 
-  string get_acct_name() const override {
-    return token_claims.user_name;
+  std::string get_acct_name() const override {
+    return this->user_name;
   }
 
   string get_subuser() const override {
@@ -438,9 +477,9 @@ public:
 
     virtual aplptr_t create_apl_web_identity( CephContext* cct,
                                               const req_state* s,
-                                              const string& role_session,
-                                              const string& role_tenant,
-                                              const rgw::web_idp::WebTokenClaims& token) const = 0;
+                                              const std::string& role_session,
+                                              const std::string& role_tenant,
+                                              const std::unordered_multimap<std::string, std::string>& token) const = 0;
   };
 };
 
index 48780c2efbb020e6dd904dd66e57193879390899..acf2d0046b108dd3bef6963ec5a20a5b6e50357b 100644 (file)
@@ -681,26 +681,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 808c2296c941fbfa9924eae109845ca8bb1c60d9..f0e542429a50710c97836aa943974259a98e9600 100644 (file)
@@ -248,7 +248,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 {
@@ -385,13 +385,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 04f94f60f3f8d10f0888f95a4bf96beb857fbf14..7f9cdf71d98388e596b3ef2fbf38f1722fdadea9 100644 (file)
@@ -806,7 +806,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 af1b96ce0d8e793a9ec3236100200d510b18b469..be3b00085124bf7e3ea1850c6a862e23df7e26eb 100644 (file)
@@ -130,6 +130,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,
@@ -141,32 +196,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;
       }
@@ -175,7 +245,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 810ff242d734f53f757adf2e8a3fcb51ecdb6f84..f3c383891258820603512c0558c033595d18a32d 100644 (file)
@@ -18,7 +18,7 @@ class WebTokenEngine : public rgw::auth::Engine {
   RGWCtl* const ctl;
 
   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;
@@ -42,6 +42,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,
                     RGWCtl* const ctl,
@@ -80,9 +83,9 @@ class DefaultStrategy : public rgw::auth::Strategy,
 
   aplptr_t create_apl_web_identity( CephContext* cct,
                                     const req_state* s,
-                                    const string& role_session,
-                                    const string& role_tenant,
-                                    const rgw::web_idp::WebTokenClaims& token) const override {
+                                    const std::string& role_session,
+                                    const std::string& role_tenant,
+                                    const std::unordered_multimap<string, string>& token) const override {
     auto apl = rgw::auth::add_sysreq(cct, ctl, s,
       rgw::auth::WebIdentityApplier(cct, ctl, role_session, role_tenant, token));
     return aplptr_t(new decltype(apl)(std::move(apl)));
index 46fd9a2ac4cdf7a4ce5b91db0c122df45be68a31..bc47ff0bf59f63f3a767b111b52afeabae715d35 100644 (file)
@@ -1018,10 +1018,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 db1eab8cf468a9197f59bbbc309b406cd231cf40..3c97c6dcd1204b3434325e8d31eed085a2ead6a6 100644 (file)
@@ -338,10 +338,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);