]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw: infrastructure for object-based Swift authentication.
authorRadoslaw Zarzynski <rzarzynski@mirantis.com>
Wed, 6 Apr 2016 16:47:29 +0000 (18:47 +0200)
committerRadoslaw Zarzynski <rzarzynski@mirantis.com>
Thu, 2 Jun 2016 13:30:38 +0000 (15:30 +0200)
Signed-off-by: Radoslaw Zarzynski <rzarzynski@mirantis.com>
src/CMakeLists.txt
src/rgw/Makefile.am
src/rgw/rgw_auth.cc
src/rgw/rgw_auth.h
src/rgw/rgw_auth_decoimpl.h [new file with mode: 0644]

index 682b5b19f4c28c966cc1ecdf9b4e6b323116d999..bb0dac4a53104505df3d5cad895903dcf235ecaf 100644 (file)
@@ -1308,6 +1308,7 @@ if(${WITH_RADOSGW})
     rgw/rgw_acl.cc
     rgw/rgw_acl_s3.cc
     rgw/rgw_acl_swift.cc
+    rgw/rgw_auth.cc
     rgw/rgw_auth_s3.cc
     rgw/rgw_basic_types.cc
     rgw/rgw_bucket.cc
index 83a9552fbaf14c7125aa2d47177cba3484daa1c5..0d8247210d7ae33059dcaae63dec604ecd82bc0b 100644 (file)
@@ -25,6 +25,7 @@ librgw_la_SOURCES = \
        rgw/rgw_acl.cc \
        rgw/rgw_acl_s3.cc \
        rgw/rgw_acl_swift.cc \
+       rgw/rgw_auth.cc \
        rgw/rgw_coroutine.cc \
        rgw/rgw_cr_rados.cc \
        rgw/rgw_tools.cc \
@@ -191,6 +192,7 @@ noinst_HEADERS += \
        rgw/rgw_acl_s3.h \
        rgw/rgw_acl_swift.h \
        rgw/rgw_auth.h \
+       rgw/rgw_auth_decoimpl.h \
        rgw/rgw_b64.h \
        rgw/rgw_client_io.h \
        rgw/rgw_coroutine.h \
index d3716a86792a848a163d3d738a578846fad47f0f..467a60dd28ea7007f2bcce520e46507acfb74210 100644 (file)
@@ -3,6 +3,10 @@
 
 #include "rgw_common.h"
 #include "rgw_auth.h"
+#include "rgw_user.h"
+#include "rgw_http_client.h"
+#include "rgw_keystone.h"
+#include "rgw_swift.h"
 
 #define dout_subsys ceph_subsys_rgw
 
@@ -68,3 +72,153 @@ rgw_auth_transform_old_authinfo(req_state * const s)
    * through any security check. */
                                     s->system_request));
 }
+
+
+/* RGWRemoteAuthApplier */
+int RGWRemoteAuthApplier::get_perms_from_aclspec(const aclspec_t& aclspec) const
+{
+  const auto iter = aclspec.find(info.auth_user.to_str());
+  if (std::end(aclspec) == iter) {
+    return iter->second;
+  }
+
+  return 0;
+}
+
+bool RGWRemoteAuthApplier::is_admin_of(const rgw_user& uid) const
+{
+  return info.is_admin;
+}
+
+bool RGWRemoteAuthApplier::is_owner_of(const rgw_user& uid) const
+{
+  if (info.acct_user.tenant.empty()) {
+    const rgw_user tenanted_acct_user(info.acct_user.id, info.acct_user.id);
+
+    if (tenanted_acct_user == uid) {
+      return true;
+    }
+  }
+
+  return info.acct_user == uid;
+}
+
+void RGWRemoteAuthApplier::create_account(const rgw_user acct_user,
+                                          RGWUserInfo& user_info) const      /* out */
+{
+  rgw_user new_acct_user = acct_user;
+
+  /* Administrator may enforce creating new accounts within their own tenants.
+   * The config parameter name is kept due to legacy. */
+  if (new_acct_user.tenant.empty() && g_conf->rgw_keystone_implicit_tenants) {
+    new_acct_user.tenant = new_acct_user.id;
+  }
+
+  user_info.user_id = new_acct_user;
+  user_info.display_name = info.acct_name;
+
+  int ret = rgw_store_user_info(store, user_info, nullptr, nullptr,
+                                real_time(), true);
+  if (ret < 0) {
+    ldout(cct, 0) << "ERROR: failed to store new user info: user="
+                  << user_info.user_id << " ret=" << ret << dendl;
+    throw ret;
+  }
+}
+
+/* TODO(rzarzynski): we need to handle display_name changes. */
+void RGWRemoteAuthApplier::load_acct_info(RGWUserInfo& user_info) const      /* out */
+{
+  /* It's supposed that RGWRemoteAuthApplier tries to load account info
+   * that belongs to the authenticated identity. Another policy may be
+   * applied by using a RGWThirdPartyAccountAuthApplier decorator. */
+  const rgw_user& acct_user = info.acct_user;
+
+  /* Normally, empty "tenant" field of acct_user means the authenticated
+   * identity has the legacy, global tenant. However, due to inclusion
+   * of multi-tenancy, we got some special compatibility kludge for remote
+   * backends like Keystone.
+   * If the global tenant is the requested one, we try the same tenant as
+   * the user name first. If that RGWUserInfo exists, we use it. This way,
+   * migrated OpenStack users can get their namespaced containers and nobody's
+   * the wiser.
+   * If that fails, we look up in the requested (possibly empty) tenant.
+   * If that fails too, we create the account within the global or separated
+   * namespace depending on rgw_keystone_implicit_tenants. */
+  if (acct_user.tenant.empty()) {
+    const rgw_user tenanted_uid(acct_user.id, acct_user.id);
+
+    if (rgw_get_user_info_by_uid(store, tenanted_uid, user_info) >= 0) {
+      /* Succeeded. */
+      return;
+    }
+  }
+
+  if (rgw_get_user_info_by_uid(store, acct_user, user_info) < 0) {
+    ldout(cct, 0) << "NOTICE: couldn't map swift user " << acct_user << dendl;
+    create_account(acct_user, user_info);
+  }
+
+  /* Succeeded if we are here (create_account() hasn't throwed). */
+}
+
+
+/* LocalAuthApplier */
+/* static declaration */
+const std::string RGWLocalAuthApplier::NO_SUBUSER;
+
+int RGWLocalAuthApplier::get_perms_from_aclspec(const aclspec_t& aclspec) const
+{
+  const auto iter = aclspec.find(user_info.user_id.to_str());
+  if (std::end(aclspec) != iter) {
+    ldout(cct, 20) << "from user ACL got perm=" << iter->second << dendl;
+    return iter->second;
+  }
+
+  return 0;
+}
+
+bool RGWLocalAuthApplier::is_admin_of(const rgw_user& uid) const
+{
+  return user_info.admin;
+}
+
+bool RGWLocalAuthApplier::is_owner_of(const rgw_user& uid) const
+{
+  return uid == user_info.user_id;
+}
+
+uint32_t RGWLocalAuthApplier::get_perm_mask(const std::string& subuser_name,
+                                            const RGWUserInfo &uinfo) const
+{
+  if (!subuser_name.empty() && subuser_name != NO_SUBUSER) {
+    const auto iter = uinfo.subusers.find(subuser_name);
+
+    if (iter != std::end(uinfo.subusers)) {
+      return iter->second.perm_mask;
+    } else {
+      /* Subuser specified but not found. */
+      return RGW_PERM_NONE;
+    }
+  } else {
+    /* Due to backward compatibility. */
+    return RGW_PERM_FULL_CONTROL;
+  }
+}
+
+void RGWLocalAuthApplier::load_acct_info(RGWUserInfo& user_info) const      /* out */
+{
+  /* Load the account that belongs to the authenticated identity. An extra call
+   * to RADOS may be safely skipped in this case. */
+  user_info = this->user_info;
+}
+
+
+RGWAuthApplier::aplptr_t RGWAnonymousAuthEngine::authenticate() const
+{
+  RGWUserInfo user_info;
+  rgw_get_anon_user(user_info);
+
+  return apl_factory->create_apl_local(cct, user_info,
+                                       RGWLocalAuthApplier::NO_SUBUSER);
+}
index bf7310074d393b0b29d05e9f75ddfb2ddbdbd53e..041218cf4e76fd725970c56de11af4dd0205f388 100644 (file)
@@ -5,7 +5,11 @@
 #ifndef CEPH_RGW_AUTH_H
 #define CEPH_RGW_AUTH_H
 
+#include <type_traits>
+
 #include "rgw_common.h"
+#include "rgw_keystone.h"
+
 #define RGW_USER_ANON_ID "anonymous"
 
 
@@ -34,13 +38,16 @@ public:
   * (account in Swift's terminology) specified in @uid. */
   virtual bool is_owner_of(const rgw_user& uid) const = 0;
 
+  /* Return the permission mask that is used to narrow down the set of
+   * operations allowed for a given identity. This method reflects the idea
+   * of subuser tied to RGWUserInfo. */
+  virtual int get_perm_mask() const = 0;
+
   virtual bool is_anonymous() const final {
     /* If the identity owns the anonymous account (rgw_user), it's considered
      * the anonymous identity. */
     return is_owner_of(rgw_user(RGW_USER_ANON_ID));
   }
-
-  virtual int get_perm_mask() const = 0;
 };
 
 inline ostream& operator<<(ostream& out, const RGWIdentityApplier &id) {
@@ -51,4 +58,243 @@ inline ostream& operator<<(ostream& out, const RGWIdentityApplier &id) {
 std::unique_ptr<RGWIdentityApplier>
 rgw_auth_transform_old_authinfo(req_state * const s);
 
+
+/* Interface for classes applying changes to request state/RADOS store imposed
+ * by a particular RGWAuthEngine.
+ *
+ * Implementations must also conform to RGWIdentityApplier interface to apply
+ * authorization policy (ACLs, account's ownership and entitlement).
+ *
+ * In contrast to RGWAuthEngine, implementations of this interface are allowed
+ * to handle req_state or RGWRados in the read-write manner. */
+class RGWAuthApplier : public RGWIdentityApplier {
+  template <typename DecorateeT> friend class RGWDecoratingAuthApplier;
+
+protected:
+  CephContext * const cct;
+
+public:
+  typedef std::unique_ptr<RGWAuthApplier> aplptr_t;
+
+  RGWAuthApplier(CephContext * const cct) : cct(cct) {}
+  virtual ~RGWAuthApplier() {};
+
+  /* Fill provided RGWUserInfo with information about the account that
+   * RGWOp will operate on. Errors are handled solely through exceptions.
+   *
+   * XXX: be aware that the "account" term refers to rgw_user. The naming
+   * is legacy. */
+  virtual void load_acct_info(RGWUserInfo& user_info) const = 0; /* out */
+
+  /* Apply any changes to request state. This method will be most useful for
+   * TempURL of Swift API or AWSv4. */
+  virtual void modify_request_state(req_state * s) const {}      /* in/out */
+};
+
+
+/* RGWRemoteAuthApplier targets those authentication engines which don't need
+ * to ask the RADOS store while performing the auth process. Instead, they
+ * obtain credentials from an external source like Keystone or LDAP.
+ *
+ * As the authenticated user may not have an account yet, RGWRemoteAuthApplier
+ * must be able to create it basing on data passed by an auth engine. Those
+ * data will be used to fill RGWUserInfo structure. */
+class RGWRemoteAuthApplier : public RGWAuthApplier {
+public:
+  class AuthInfo {
+    friend class RGWRemoteAuthApplier;
+  protected:
+    const rgw_user acct_user;
+    const std::string acct_name;
+    const uint32_t perm_mask;
+    const bool is_admin;
+
+  public:
+    AuthInfo(const rgw_user acct_user,
+             const std::string acct_name,
+             const uint32_t perm_mask,
+             const bool is_admin)
+    : acct_user(acct_user),
+      acct_name(acct_name),
+      perm_mask(perm_mask),
+      is_admin(is_admin) {
+    }
+  };
+
+protected:
+  /* Read-write is intensional here due to RGWUserInfo creation process. */
+  RGWRados * const store;
+  const AuthInfo info;
+
+
+  virtual void create_account(const rgw_user acct_user,
+                              RGWUserInfo& user_info) const;          /* out */
+
+public:
+  RGWRemoteAuthApplier(CephContext * const cct,
+                       RGWRados * const store,
+                       const AuthInfo info)
+    : RGWAuthApplier(cct),
+      store(store),
+      info(info) {
+  }
+
+  virtual int get_perms_from_aclspec(const aclspec_t& aclspec) const override;
+  virtual bool is_admin_of(const rgw_user& uid) const override;
+  virtual bool is_owner_of(const rgw_user& uid) const override;
+  virtual int get_perm_mask() const { return info.perm_mask; }
+  virtual void load_acct_info(RGWUserInfo& user_info) const override; /* out */
+
+  struct Factory {
+    virtual ~Factory() {}
+    virtual aplptr_t create_apl_remote(CephContext * const cct,
+                                       const AuthInfo info) const = 0;
+  };
+};
+
+
+/* Local auth applier targets those auth engines that store user information
+ * in the RADOS store. As a consequence of performing the authentication, they
+ * will have the RGWUserInfo structure loaded. Exploiting this trait allows to
+ * avoid additional call to underlying RADOS store. */
+class RGWLocalAuthApplier : public RGWAuthApplier {
+protected:
+  const RGWUserInfo user_info;
+  const std::string subuser;
+
+  uint32_t get_perm_mask(const std::string& subuser_name,
+                         const RGWUserInfo &uinfo) const;
+
+public:
+  static const std::string NO_SUBUSER;
+
+  RGWLocalAuthApplier(CephContext * const cct,
+                      const RGWUserInfo& user_info,
+                      const std::string subuser)
+    : RGWAuthApplier(cct),
+      user_info(user_info),
+      subuser(subuser) {
+  }
+
+
+  virtual int get_perms_from_aclspec(const aclspec_t& aclspec) const override;
+  virtual bool is_admin_of(const rgw_user& uid) const override;
+  virtual bool is_owner_of(const rgw_user& uid) const override;
+  virtual int get_perm_mask() const override {
+    return get_perm_mask(subuser, user_info);
+  }
+  virtual void load_acct_info(RGWUserInfo& user_info) const override; /* out */
+
+  struct Factory {
+    virtual ~Factory() {}
+    virtual aplptr_t create_apl_local(CephContext * const cct,
+                                      const RGWUserInfo& user_info,
+                                      const std::string& subuser) const = 0;
+    };
+};
+
+
+/* Abstract class for authentication backends (auth engines) in RadosGW.
+ *
+ * An engine is supposed only to:
+ *  - authenticate (not authorize!) a given request basing on req_state,
+ *  - provide an upper layer with RGWAuthApplier to commit all changes to
+ *    data structures (like req_state) and to the RADOS store (creating
+ *    an account, synchronizing user personal info).
+ *    Auth engine MUST NOT make any changes to req_state nor RADOS store.
+ *
+ * Separation between authentication and global state modification has been
+ * introduced because many auth engines are perfectly orthogonal to applier
+ * and thus they can be decoupled. Additional motivation is clearly distinguish
+ * all places which can modify underlying data structures. */
+class RGWAuthEngine {
+protected:
+  CephContext * const cct;
+
+  RGWAuthEngine(CephContext * const cct)
+    : cct(cct) {
+  }
+  /* Make the engines non-copyable and non-moveable due to const-correctness
+   * and aggregating applier factories less costly and error-prone. */
+  RGWAuthEngine(const RGWAuthEngine&) = delete;
+  RGWAuthEngine& operator=(const RGWAuthEngine&) = delete;
+
+public:
+  /* Fast, non-throwing method for screening whether a concrete engine may
+   * be interested in handling a specific request. */
+  virtual bool is_applicable() const noexcept = 0;
+
+  /* Throwing method for identity verification. When the check is positive
+   * an implementation should return RGWAuthApplier::aplptr_t containing
+   * a non-null pointer to object conforming the RGWAuthApplier interface.
+   * Otherwise, the authentication is treated as failed.
+   * An error may be signalised by throwing an exception of int type with
+   * errno value inside. Those value are always negative. */
+  virtual RGWAuthApplier::aplptr_t authenticate() const = 0;
+
+  virtual ~RGWAuthEngine() {};
+};
+
+
+/* Abstract base class for all token-based auth engines. */
+class RGWTokenBasedAuthEngine : public RGWAuthEngine {
+protected:
+  const std::string token;
+
+public:
+  RGWTokenBasedAuthEngine(CephContext * const cct,
+                          const std::string token)
+    : RGWAuthEngine(cct),
+      token(token) {
+  }
+
+  bool is_applicable() const noexcept override {
+    return !token.empty();
+  }
+};
+
+/* TODO: introduce extractors for TokenBased. */
+
+/* Keystone. */
+class RGWKeystoneAuthEngine : public RGWTokenBasedAuthEngine {
+protected:
+  const RGWRemoteAuthApplier::Factory * const apl_factory;
+
+  /* Helper methods. */
+  KeystoneToken decode_pki_token(const std::string token) const;
+  KeystoneToken get_from_keystone(const std::string token) const;
+  RGWRemoteAuthApplier::AuthInfo get_creds_info(const KeystoneToken& token,
+                                                const std::vector<std::string>& admin_roles
+                                               ) const noexcept;
+public:
+  RGWKeystoneAuthEngine(CephContext * const cct,
+                        const std::string token,
+                        const RGWRemoteAuthApplier::Factory * const apl_factory)
+    : RGWTokenBasedAuthEngine(cct, token),
+      apl_factory(apl_factory) {
+  }
+
+  bool is_applicable() const noexcept override;
+  RGWAuthApplier::aplptr_t authenticate() const override;
+};
+
+
+/* Anonymous */
+class RGWAnonymousAuthEngine : public RGWAuthEngine {
+  const RGWLocalAuthApplier::Factory * const apl_factory;
+
+public:
+  RGWAnonymousAuthEngine(CephContext * const cct,
+                         const RGWLocalAuthApplier::Factory * const apl_factory)
+    : RGWAuthEngine(cct),
+      apl_factory(apl_factory) {
+  }
+
+  bool is_applicable() const noexcept override {
+    return true;
+  }
+
+  RGWAuthApplier::aplptr_t authenticate() const override;
+};
+
 #endif /* CEPH_RGW_AUTH_H */
diff --git a/src/rgw/rgw_auth_decoimpl.h b/src/rgw/rgw_auth_decoimpl.h
new file mode 100644 (file)
index 0000000..fa734b1
--- /dev/null
@@ -0,0 +1,145 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+
+#ifndef CEPH_RGW_AUTH_DECOIMPL_H
+#define CEPH_RGW_AUTH_DECOIMPL_H
+
+#include "rgw_auth.h"
+
+
+/* Abstract decorator over any implementation of RGWAuthApplier. */
+template <typename DecorateeT>
+class RGWDecoratingAuthApplier : public RGWAuthApplier {
+  static_assert(std::is_base_of<RGWAuthApplier, DecorateeT>::value,
+                "DecorateeT must be a subclass of RGWAuthApplier");
+  DecorateeT decoratee;
+
+public:
+  RGWDecoratingAuthApplier(const DecorateeT& decoratee)
+    : RGWAuthApplier(decoratee.cct),
+      decoratee(decoratee) {
+  }
+
+  virtual int get_perms_from_aclspec(const aclspec_t& aclspec) const override {
+    return decoratee.get_perms_from_aclspec(aclspec);
+  }
+
+  virtual bool is_admin_of(const rgw_user& uid) const override {
+    return decoratee.is_admin_of(uid);
+  }
+
+  virtual bool is_owner_of(const rgw_user& uid) const override {
+    return decoratee.is_owner_of(uid);
+  }
+
+  virtual int get_perm_mask() const override {
+    return decoratee.get_perm_mask();
+  }
+
+  virtual void load_acct_info(RGWUserInfo& user_info) const override {  /* out */
+    return decoratee.load_acct_info(user_info);
+  }
+
+  virtual void modify_request_state(req_state * s) const override {     /* in/out */
+    return decoratee.modify_request_state(s);
+  }
+};
+
+
+/* Decorator specialization for dealing with pointers to an applier. Useful
+ * for decorating the applier returned after successfull authenication. */
+template <>
+class RGWDecoratingAuthApplier<RGWAuthApplier::aplptr_t> : public RGWAuthApplier {
+  aplptr_t decoratee;
+
+public:
+  RGWDecoratingAuthApplier(aplptr_t&& decoratee)
+    : RGWAuthApplier(decoratee->cct),
+      decoratee(std::move(decoratee)) {
+  }
+
+  virtual int get_perms_from_aclspec(const aclspec_t& aclspec) const override {
+    return decoratee->get_perms_from_aclspec(aclspec);
+  }
+
+  virtual bool is_admin_of(const rgw_user& uid) const override {
+    return decoratee->is_admin_of(uid);
+  }
+
+  virtual bool is_owner_of(const rgw_user& uid) const override {
+    return decoratee->is_owner_of(uid);
+  }
+
+  virtual int get_perm_mask() const override {
+    return decoratee->get_perm_mask();
+  }
+
+  virtual void load_acct_info(RGWUserInfo& user_info) const override {  /* out */
+    return decoratee->load_acct_info(user_info);
+  }
+
+  virtual void modify_request_state(req_state * s) const override {     /* in/out */
+    return decoratee->modify_request_state(s);
+  }
+};
+
+
+template <typename T>
+class RGWThirdPartyAccountAuthApplier : public RGWDecoratingAuthApplier<T> {
+  /* const */RGWRados * const store;
+  const rgw_user acct_user_override;
+public:
+  /* FIXME: comment this. */
+  static const rgw_user UNKNOWN_ACCT;
+
+  template <typename U>
+  RGWThirdPartyAccountAuthApplier(U&& decoratee,
+                                  RGWRados * const store,
+                                  const rgw_user acct_user_override)
+    : RGWDecoratingAuthApplier<T>(std::move(decoratee)),
+      store(store),
+      acct_user_override(acct_user_override) {
+  }
+
+  virtual void load_acct_info(RGWUserInfo& user_info) const override;   /* out */
+};
+
+/* static declaration */
+template <typename T>
+const rgw_user RGWThirdPartyAccountAuthApplier<T>::UNKNOWN_ACCT;
+
+template <typename T>
+void RGWThirdPartyAccountAuthApplier<T>::load_acct_info(RGWUserInfo& user_info) const
+{
+  if (UNKNOWN_ACCT == acct_user_override) {
+    /* There is no override specified by the upper layer. This means that we'll
+     * load the account owned by the authenticated identity (aka auth_user). */
+    RGWDecoratingAuthApplier<T>::load_acct_info(user_info);
+  } else if (RGWDecoratingAuthApplier<T>::is_owner_of(acct_user_override)) {
+    /* The override has been specified but the account belongs to the authenticated
+     * identity. We may safely forward the call to a next stage. */
+    RGWDecoratingAuthApplier<T>::load_acct_info(user_info);
+  } else {
+    /* Compatibility mechanism for multi-tenancy. For more details refer to
+     * load_acct_info method of RGWRemoteAuthApplier. */
+    if (acct_user_override.tenant.empty()) {
+      const rgw_user tenanted_uid(acct_user_override.id, acct_user_override.id);
+
+      if (rgw_get_user_info_by_uid(store, tenanted_uid, user_info) >= 0) {
+        /* Succeeded. */
+        return;
+      }
+    }
+
+    int ret = rgw_get_user_info_by_uid(store, acct_user_override, user_info);
+    if (ret < 0) {
+      /* We aren't trying to recover from ENOENT here. It's supposed that creating
+       * someone else's account isn't a thing we want to support in this filter. */
+      throw ret;
+    }
+
+  }
+}
+
+#endif /* CEPH_RGW_AUTH_DECOIMPL_H */