From b7bd73913764ad65bb022ad4fab3f68715cec399 Mon Sep 17 00:00:00 2001 From: Radoslaw Zarzynski Date: Wed, 6 Apr 2016 18:47:29 +0200 Subject: [PATCH] rgw: infrastructure for object-based Swift authentication. Signed-off-by: Radoslaw Zarzynski --- src/CMakeLists.txt | 1 + src/rgw/Makefile.am | 2 + src/rgw/rgw_auth.cc | 154 ++++++++++++++++++++++ src/rgw/rgw_auth.h | 250 +++++++++++++++++++++++++++++++++++- src/rgw/rgw_auth_decoimpl.h | 145 +++++++++++++++++++++ 5 files changed, 550 insertions(+), 2 deletions(-) create mode 100644 src/rgw/rgw_auth_decoimpl.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 682b5b19f4c28..bb0dac4a53104 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/rgw/Makefile.am b/src/rgw/Makefile.am index 83a9552fbaf14..0d8247210d7ae 100644 --- a/src/rgw/Makefile.am +++ b/src/rgw/Makefile.am @@ -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 \ diff --git a/src/rgw/rgw_auth.cc b/src/rgw/rgw_auth.cc index d3716a86792a8..467a60dd28ea7 100644 --- a/src/rgw/rgw_auth.cc +++ b/src/rgw/rgw_auth.cc @@ -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); +} diff --git a/src/rgw/rgw_auth.h b/src/rgw/rgw_auth.h index bf7310074d393..041218cf4e76f 100644 --- a/src/rgw/rgw_auth.h +++ b/src/rgw/rgw_auth.h @@ -5,7 +5,11 @@ #ifndef CEPH_RGW_AUTH_H #define CEPH_RGW_AUTH_H +#include + #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 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 friend class RGWDecoratingAuthApplier; + +protected: + CephContext * const cct; + +public: + typedef std::unique_ptr 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& 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 index 0000000000000..fa734b12f2c40 --- /dev/null +++ b/src/rgw/rgw_auth_decoimpl.h @@ -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 +class RGWDecoratingAuthApplier : public RGWAuthApplier { + static_assert(std::is_base_of::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 : 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 +class RGWThirdPartyAccountAuthApplier : public RGWDecoratingAuthApplier { + /* const */RGWRados * const store; + const rgw_user acct_user_override; +public: + /* FIXME: comment this. */ + static const rgw_user UNKNOWN_ACCT; + + template + RGWThirdPartyAccountAuthApplier(U&& decoratee, + RGWRados * const store, + const rgw_user acct_user_override) + : RGWDecoratingAuthApplier(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 +const rgw_user RGWThirdPartyAccountAuthApplier::UNKNOWN_ACCT; + +template +void RGWThirdPartyAccountAuthApplier::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::load_acct_info(user_info); + } else if (RGWDecoratingAuthApplier::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::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 */ -- 2.39.5