#include "rgw_user.h"
#include "rgw_http_client.h"
#include "rgw_keystone.h"
-#include "rgw_swift.h"
+
+#include "include/str_list.h"
#define dout_subsys ceph_subsys_rgw
return apl_factory->create_apl_local(cct, user_info,
RGWLocalAuthApplier::NO_SUBUSER);
}
+
+
+/* Keystone */
+class RGWKeystoneHTTPTransceiver : public RGWHTTPTransceiver {
+public:
+ RGWKeystoneHTTPTransceiver(CephContext * const cct,
+ bufferlist * const token_body_bl)
+ : RGWHTTPTransceiver(cct, token_body_bl,
+ cct->_conf->rgw_keystone_verify_ssl,
+ { "X-Subject-Token" }) {
+ }
+
+ std::string get_subject_token() const {
+ try {
+ return get_header_value("X-Subject-Token");
+ } catch (std::out_of_range&) {
+ return header_value_t();
+ }
+ }
+};
+
+typedef RGWKeystoneHTTPTransceiver RGWValidateKeystoneToken;
+typedef RGWKeystoneHTTPTransceiver RGWGetKeystoneAdminToken;
+typedef RGWKeystoneHTTPTransceiver RGWGetRevokedTokens;
+
+
+bool RGWKeystoneAuthEngine::is_applicable() const noexcept
+{
+ if (false == RGWTokenBasedAuthEngine::is_applicable()) {
+ return false;
+ }
+
+ return false == cct->_conf->rgw_keystone_url.empty();
+}
+
+KeystoneToken RGWKeystoneAuthEngine::decode_pki_token(const std::string token) const
+{
+ bufferlist token_body_bl;
+ int ret = rgw_decode_b64_cms(cct, token, token_body_bl);
+ if (ret < 0) {
+ ldout(cct, 20) << "cannot decode pki token" << dendl;
+ throw ret;
+ } else {
+ ldout(cct, 20) << "successfully decoded pki token" << dendl;
+ }
+
+ KeystoneToken token_body;
+ ret = token_body.parse(cct, token, token_body_bl);
+ if (ret < 0) {
+ throw ret;
+ }
+
+ return token_body;
+}
+
+KeystoneToken RGWKeystoneAuthEngine::get_from_keystone(const std::string token) const
+{
+ bufferlist token_body_bl;
+ RGWValidateKeystoneToken validate(cct, &token_body_bl);
+
+ std::string url;
+ if (KeystoneService::get_keystone_url(cct, url) < 0) {
+ throw -EINVAL;
+ }
+
+ const auto keystone_version = KeystoneService::get_api_version();
+ if (keystone_version == KeystoneApiVersion::VER_2) {
+ url.append("v2.0/tokens/" + token);
+ } else if (keystone_version == KeystoneApiVersion::VER_3) {
+ url.append("v3/auth/tokens");
+ validate.append_header("X-Subject-Token", token);
+ }
+
+ std::string admin_token;
+ if (KeystoneService::get_keystone_admin_token(cct, admin_token) < 0) {
+ throw -EINVAL;
+ }
+
+ validate.append_header("X-Auth-Token", admin_token);
+ validate.set_send_length(0);
+
+ int ret = validate.process(url.c_str());
+ if (ret < 0) {
+ throw ret;
+ }
+ token_body_bl.append((char)0); // NULL terminate for debug output
+
+ ldout(cct, 20) << "received response: " << token_body_bl.c_str() << dendl;
+
+ KeystoneToken token_body;
+ ret = token_body.parse(cct, token, token_body_bl);
+ if (ret < 0) {
+ throw ret;
+ }
+
+ return token_body;
+}
+
+RGWRemoteAuthApplier::AuthInfo
+RGWKeystoneAuthEngine::get_creds_info(const KeystoneToken& token,
+ const std::vector<std::string>& admin_roles
+ ) const noexcept
+{
+ /* Check whether the user has an admin status. */
+ bool is_admin = false;
+ for (const auto admin_role : admin_roles) {
+ if (token.has_role(admin_role)) {
+ is_admin = true;
+ break;
+ }
+ }
+
+ return {
+ /* Suggested account name for the authenticated user. */
+ rgw_user(token.get_project_id()),
+ /* User's display name (aka real name). */
+ token.get_project_name(),
+ /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
+ * the access rights through the perm_mask. At least at this layer. */
+ RGW_PERM_FULL_CONTROL,
+ is_admin,
+ };
+}
+
+RGWAuthApplier::aplptr_t RGWKeystoneAuthEngine::authenticate() const
+{
+ KeystoneToken t;
+
+ /* This will be initialized on the first call to this method. In C++11 it's
+ * also thread-safe. */
+ static struct RolesCacher {
+ RolesCacher(CephContext * const cct) {
+ get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain);
+ get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin);
+
+ /* Let's suppose that having an admin role implies also a regular one. */
+ plain.insert(std::end(plain), std::begin(admin), std::end(admin));
+ }
+
+ std::vector<std::string> plain;
+ std::vector<std::string> admin;
+ } roles(cct);
+
+ /* Token ID is a concept that makes dealing with PKI tokens more effective.
+ * Instead of storing several kilobytes, a short hash can be burried. */
+ const auto token_id = rgw_get_token_id(token);
+ ldout(cct, 20) << "token_id=" << token_id << dendl;
+
+ /* Check cache first. */
+ if (RGWKeystoneTokenCache::get_instance().find(token_id, t)) {
+ ldout(cct, 20) << "cached token.project.id=" << t.get_project_id()
+ << dendl;
+ return apl_factory->create_apl_remote(cct,
+ get_creds_info(t, roles.admin));
+ }
+
+ /* Retrieve token. */
+ if (rgw_is_pki_token(token)) {
+ try {
+ t = decode_pki_token(token);
+ } catch (...) {
+ /* Last resort. */
+ t = get_from_keystone(token);
+ }
+ } else {
+ /* Can't decode, just go to the Keystone server for validation. */
+ t = get_from_keystone(token);
+ }
+
+ /* Verify expiration. */
+ if (t.expired()) {
+ ldout(cct, 0) << "got expired token: " << t.get_project_name()
+ << ":" << t.get_user_name()
+ << " expired: " << t.get_expires() << dendl;
+ return nullptr;
+ }
+
+ /* Check for necessary roles. */
+ for (const auto role : roles.plain) {
+ if (t.has_role(role) == true) {
+ ldout(cct, 0) << "validated token: " << t.get_project_name()
+ << ":" << t.get_user_name()
+ << " expires: " << t.get_expires() << dendl;
+ RGWKeystoneTokenCache::get_instance().add(token_id, t);
+ return apl_factory->create_apl_remote(cct,
+ get_creds_info(t, roles.admin));
+ }
+ }
+
+ ldout(cct, 0) << "user does not hold a matching role; required roles: "
+ << g_conf->rgw_keystone_accepted_roles << dendl;
+
+ return nullptr;
+}