]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/rgw/rgw_rest_sts.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / rgw / rgw_rest_sts.cc
index af1b96ce0d8e793a9ec3236100200d510b18b469..093c92d3633ea5e2c5d16eb4a67e9dc6ffc6d0eb 100644 (file)
@@ -41,6 +41,8 @@
 #define dout_context g_ceph_context
 #define dout_subsys ceph_subsys_rgw
 
+using namespace std;
+
 namespace rgw::auth::sts {
 
 bool
@@ -60,7 +62,24 @@ WebTokenEngine::get_role_tenant(const string& role_arn) const
   return tenant;
 }
 
-boost::optional<RGWOIDCProvider>
+std::string
+WebTokenEngine::get_role_name(const string& role_arn) const
+{
+  string role_name;
+  auto r_arn = rgw::ARN::parse(role_arn);
+  if (r_arn) {
+    role_name = r_arn->resource;
+  }
+  if (!role_name.empty()) {
+    auto pos = role_name.find_last_of('/');
+    if(pos != string::npos) {
+      role_name = role_name.substr(pos + 1);
+    }
+  }
+  return role_name;
+}
+
+std::unique_ptr<rgw::sal::RGWOIDCProvider>
 WebTokenEngine::get_provider(const DoutPrefixProvider *dpp, const string& role_arn, const string& iss) const
 {
   string tenant = get_role_tenant(role_arn);
@@ -82,10 +101,12 @@ WebTokenEngine::get_provider(const DoutPrefixProvider *dpp, const string& role_a
   }
   auto provider_arn = rgw::ARN(idp_url, "oidc-provider", tenant);
   string p_arn = provider_arn.to_string();
-  RGWOIDCProvider provider(cct, ctl, p_arn, tenant);
-  auto ret = provider.get(dpp);
+  std::unique_ptr<rgw::sal::RGWOIDCProvider> provider = store->get_oidc_provider();
+  provider->set_arn(p_arn);
+  provider->set_tenant(tenant);
+  auto ret = provider->get(dpp);
   if (ret < 0) {
-    return boost::none;
+    return nullptr;
   }
   return provider;
 }
@@ -130,43 +151,131 @@ 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, T& t) const
+{
+  string s_val;
+  jwt::claim::type c_type = c.get_type();
+  switch(c_type) {
+    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();
+      t.emplace(std::make_pair(key, s_val));
+      break;
+    }
+    case jwt::claim::type::string:
+    {
+      s_val = c.to_json().to_str();
+      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), t);
+      }
+      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), t);
+      }
+      break;
+    }
+  }
+  return;
+}
+
+//Extract all token claims so that they can be later used in the Condition element of Role's trust policy
+WebTokenEngine::token_t
+WebTokenEngine::get_token_claims(const jwt::decoded_jwt& decoded) const
+{
+  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);
 
     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;
     }
+    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()) {
-      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,33 +284,71 @@ 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;
       }
     } 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, principal_tags};
+}
+
+std::string
+WebTokenEngine::get_cert_url(const string& iss, const DoutPrefixProvider *dpp, optional_yield y) const
+{
+  string cert_url;
+  string openidc_wellknown_url = iss + "/.well-known/openid-configuration";
+  bufferlist openidc_resp;
+  RGWHTTPTransceiver openidc_req(cct, "GET", openidc_wellknown_url, &openidc_resp);
+
+  //Headers
+  openidc_req.append_header("Content-Type", "application/x-www-form-urlencoded");
+
+  int res = openidc_req.process(y);
+  if (res < 0) {
+    ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl;
+    throw -EINVAL;
+  }
+
+  //Debug only
+  ldpp_dout(dpp, 20) << "HTTP status: " << openidc_req.get_http_status() << dendl;
+  ldpp_dout(dpp, 20) << "JSON Response is: " << openidc_resp.c_str() << dendl;
+
+  JSONParser parser;
+  if (parser.parse(openidc_resp.c_str(), openidc_resp.length())) {
+    JSONObj::data_val val;
+    if (parser.get_data("jwks_uri", &val)) {
+      cert_url = val.str.c_str();
+      ldpp_dout(dpp, 20) << "Cert URL is: " << cert_url.c_str() << dendl;
+    } else {
+      ldpp_dout(dpp, 0) << "Malformed json returned while fetching openidc url" << dendl;
+    }
   }
-  return t;
+  return cert_url;
 }
 
 void
 WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector<string>& thumbprints, optional_yield y) const
 {
   if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") {
+    string cert_url = get_cert_url(iss, dpp, y);
+    if (cert_url.empty()) {
+      throw -EINVAL;
+    }
+
     // Get certificate
-    string cert_url = iss + "/protocol/openid-connect/certs";
     bufferlist cert_resp;
     RGWHTTPTransceiver cert_req(cct, "GET", cert_url, &cert_resp);
     //Headers
@@ -328,31 +475,36 @@ 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);
+      string role_name = get_role_name(role_arn);
+      std::unique_ptr<rgw::sal::RGWRole> role = store->get_role(role_name, role_tenant);
+      int ret = role->get(dpp, y);
+      if (ret < 0) {
+        ldpp_dout(dpp, 0) << "Role not found: name:" << role_name << " tenant: " << role_tenant << dendl;
+        return result_t::deny(-EACCES);
+      }
+      boost::optional<multimap<string,string>> role_tags = role->get_tags();
+      auto apl = apl_factory->create_apl_web_identity(cct, s, role_session, role_tenant, *t, role_tags, 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
@@ -368,23 +520,30 @@ int RGWREST_STS::verify_permission(optional_yield y)
     ldpp_dout(this, 0) << "failed to get role info using role arn: " << rArn << dendl;
     return ret;
   }
-  string policy = role.get_assume_role_policy();
+  string policy = role->get_assume_role_policy();
   buffer::list bl = buffer::list::static_from_string(policy);
 
   //Parse the policy
   //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, boost::none);
+      if (res != rgw::IAM::Effect::Allow) {
+        ldout(s->cct, 0) << "evaluating policy for stsTagSession returned deny/pass" << dendl;
+        return -EPERM;
+      }
+    }
+    uint64_t op;
+    if (get_type() == RGW_STS_ASSUME_ROLE_WEB_IDENTITY) {
+      op = rgw::IAM::stsAssumeRoleWithWebIdentity;
+    } else {
+      op = rgw::IAM::stsAssumeRole;
     }
-    auto c_res = p.eval_conditions(s->env);
-    if (c_res == rgw::IAM::Effect::Deny) {
-      ldpp_dout(this, 0) << "evaluating condition returned deny" << dendl;
+
+    auto res = p.eval(s->env, *s->auth.identity, op, boost::none);
+    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) {
@@ -452,7 +611,7 @@ void RGWSTSGetSessionToken::execute(optional_yield y)
   STS::STSService sts(s->cct, store, s->user->get_id(), s->auth.identity.get());
 
   STS::GetSessionTokenRequest req(duration, serialNumber, tokenCode);
-  const auto& [ret, creds] = sts.getSessionToken(req);
+  const auto& [ret, creds] = sts.getSessionToken(this, req);
   op_ret = std::move(ret);
   //Dump the output
   if (op_ret == 0) {
@@ -503,8 +662,8 @@ void RGWSTSAssumeRoleWithWebIdentity::execute(optional_yield y)
   }
 
   STS::AssumeRoleWithWebIdentityRequest req(s->cct, duration, providerId, policy, roleArn,
-                        roleSessionName, iss, sub, aud);
-  STS::AssumeRoleWithWebIdentityResponse response = sts.assumeRoleWithWebIdentity(req);
+                        roleSessionName, iss, sub, aud, s->principal_tags);
+  STS::AssumeRoleWithWebIdentityResponse response = sts.assumeRoleWithWebIdentity(this, req);
   op_ret = std::move(response.assumeRoleResp.retCode);
 
   //Dump the output
@@ -582,7 +741,7 @@ void RGWSTSAssumeRole::execute(optional_yield y)
 }
 
 int RGW_Auth_STS::authorize(const DoutPrefixProvider *dpp,
-                            rgw::sal::RGWRadosStore *store,
+                            rgw::sal::Store* store,
                             const rgw::auth::StrategyRegistry& auth_registry,
                             struct req_state *s, optional_yield y)
 {
@@ -628,7 +787,7 @@ RGWOp *RGWHandler_REST_STS::op_post()
   return nullptr;
 }
 
-int RGWHandler_REST_STS::init(rgw::sal::RGWRadosStore *store,
+int RGWHandler_REST_STS::init(rgw::sal::Store* store,
                               struct req_state *s,
                               rgw::io::BasicClient *cio)
 {
@@ -693,7 +852,7 @@ int RGWHandler_REST_STS::init_from_header(struct req_state* s,
 }
 
 RGWHandler_REST*
-RGWRESTMgr_STS::get_handler(rgw::sal::RGWRadosStore *store,
+RGWRESTMgr_STS::get_handler(rgw::sal::Store* store,
                            struct req_state* const s,
                            const rgw::auth::StrategyRegistry& auth_registry,
                            const std::string& frontend_prefix)