X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Frgw%2Frgw_auth_s3.h;fp=src%2Fceph%2Fsrc%2Frgw%2Frgw_auth_s3.h;h=1c851a933e1138baf7528428f113acc5663da3ae;hb=812ff6ca9fcd3e629e49d4328905f33eee8ca3f5;hp=0000000000000000000000000000000000000000;hpb=15280273faafb77777eab341909a3f495cf248d9;p=stor4nfv.git diff --git a/src/ceph/src/rgw/rgw_auth_s3.h b/src/ceph/src/rgw/rgw_auth_s3.h new file mode 100644 index 0000000..1c851a9 --- /dev/null +++ b/src/ceph/src/rgw/rgw_auth_s3.h @@ -0,0 +1,450 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#ifndef CEPH_RGW_AUTH_S3_H +#define CEPH_RGW_AUTH_S3_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/sstring.hh" +#include "rgw_common.h" +#include "rgw_rest_s3.h" + +#include "rgw_auth.h" +#include "rgw_auth_filters.h" +#include "rgw_auth_keystone.h" + + +namespace rgw { +namespace auth { +namespace s3 { + +class ExternalAuthStrategy : public rgw::auth::Strategy, + public rgw::auth::RemoteApplier::Factory { + typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; + RGWRados* const store; + + using keystone_config_t = rgw::keystone::CephCtxConfig; + using keystone_cache_t = rgw::keystone::TokenCache; + using EC2Engine = rgw::auth::keystone::EC2Engine; + + boost::optional keystone_engine; + LDAPEngine ldap_engine; + + aplptr_t create_apl_remote(CephContext* const cct, + const req_state* const s, + rgw::auth::RemoteApplier::acl_strategy_t&& acl_alg, + const rgw::auth::RemoteApplier::AuthInfo info + ) const override { + auto apl = rgw::auth::add_sysreq(cct, store, s, + rgw::auth::RemoteApplier(cct, store, std::move(acl_alg), info, + cct->_conf->rgw_keystone_implicit_tenants)); + /* TODO(rzarzynski): replace with static_ptr. */ + return aplptr_t(new decltype(apl)(std::move(apl))); + } + +public: + ExternalAuthStrategy(CephContext* const cct, + RGWRados* const store, + AWSEngine::VersionAbstractor* const ver_abstractor) + : store(store), + ldap_engine(cct, store, *ver_abstractor, + static_cast(this)) { + + if (cct->_conf->rgw_s3_auth_use_keystone && + ! cct->_conf->rgw_keystone_url.empty()) { + + keystone_engine.emplace(cct, ver_abstractor, + static_cast(this), + keystone_config_t::get_instance(), + keystone_cache_t::get_instance()); + add_engine(Control::SUFFICIENT, *keystone_engine); + + } + + if (cct->_conf->rgw_s3_auth_use_ldap && + ! cct->_conf->rgw_ldap_uri.empty()) { + add_engine(Control::SUFFICIENT, ldap_engine); + } + } + + const char* get_name() const noexcept override { + return "rgw::auth::s3::AWSv2ExternalAuthStrategy"; + } +}; + + +template +class AWSAuthStrategy : public rgw::auth::Strategy, + public rgw::auth::LocalApplier::Factory { + typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; + + static_assert(std::is_base_of::value, + "AbstractorT must be a subclass of rgw::auth::s3::VersionAbstractor"); + + RGWRados* const store; + AbstractorT ver_abstractor; + + S3AnonymousEngine anonymous_engine; + ExternalAuthStrategy external_engines; + LocalEngine local_engine; + + aplptr_t create_apl_local(CephContext* const cct, + const req_state* const s, + const RGWUserInfo& user_info, + const std::string& subuser) const override { + auto apl = rgw::auth::add_sysreq(cct, store, s, + rgw::auth::LocalApplier(cct, user_info, subuser)); + /* TODO(rzarzynski): replace with static_ptr. */ + return aplptr_t(new decltype(apl)(std::move(apl))); + } + +public: + AWSAuthStrategy(CephContext* const cct, + RGWRados* const store) + : store(store), + ver_abstractor(cct), + anonymous_engine(cct, + static_cast(this)), + external_engines(cct, store, &ver_abstractor), + local_engine(cct, store, ver_abstractor, + static_cast(this)) { + /* The anynoymous auth. */ + if (AllowAnonAccessT) { + add_engine(Control::SUFFICIENT, anonymous_engine); + } + + /* The external auth. */ + Control local_engine_mode; + if (! external_engines.is_empty()) { + add_engine(Control::SUFFICIENT, external_engines); + + local_engine_mode = Control::FALLBACK; + } else { + local_engine_mode = Control::SUFFICIENT; + } + + /* The local auth. */ + if (cct->_conf->rgw_s3_auth_use_rados) { + add_engine(local_engine_mode, local_engine); + } + } + + const char* get_name() const noexcept override { + return "rgw::auth::s3::AWSAuthStrategy"; + } +}; + + +class AWSv4ComplMulti : public rgw::auth::Completer, + public rgw::io::DecoratedRestfulClient, + public std::enable_shared_from_this { + using io_base_t = rgw::io::DecoratedRestfulClient; + using signing_key_t = std::array; + + CephContext* const cct; + + const boost::string_view date; + const boost::string_view credential_scope; + const signing_key_t signing_key; + + class ChunkMeta { + size_t data_offset_in_stream = 0; + size_t data_length = 0; + std::string signature; + + ChunkMeta(const size_t data_starts_in_stream, + const size_t data_length, + const boost::string_ref signature) + : data_offset_in_stream(data_starts_in_stream), + data_length(data_length), + signature(signature.to_string()) { + } + + ChunkMeta(const boost::string_view& signature) + : signature(signature.to_string()) { + } + + public: + static constexpr size_t SIG_SIZE = 64; + + /* Let's suppose the data length fields can't exceed uint64_t. */ + static constexpr size_t META_MAX_SIZE = \ + sarrlen("\r\nffffffffffffffff;chunk-signature=") + SIG_SIZE + sarrlen("\r\n"); + + /* The metadata size of for the last, empty chunk. */ + static constexpr size_t META_MIN_SIZE = \ + sarrlen("0;chunk-signature=") + SIG_SIZE + sarrlen("\r\n"); + + /* Detect whether a given stream_pos fits in boundaries of a chunk. */ + bool is_new_chunk_in_stream(size_t stream_pos) const; + + /* Get the remaining data size. */ + size_t get_data_size(size_t stream_pos) const; + + const std::string& get_signature() const { + return signature; + } + + /* Factory: create an object representing metadata of first, initial chunk + * in a stream. */ + static ChunkMeta create_first(const boost::string_view& seed_signature) { + return ChunkMeta(seed_signature); + } + + /* Factory: parse a block of META_MAX_SIZE bytes and creates an object + * representing non-first chunk in a stream. As the process is sequential + * and depends on the previous chunk, caller must pass it. */ + static std::pair create_next(CephContext* cct, + ChunkMeta&& prev, + const char* metabuf, + size_t metabuf_len); + } chunk_meta; + + size_t stream_pos; + boost::container::static_vector parsing_buf; + ceph::crypto::SHA256* sha256_hash; + std::string prev_chunk_signature; + + bool is_signature_mismatched(); + std::string calc_chunk_signature(const std::string& payload_hash) const; + +public: + /* We need the constructor to be public because of the std::make_shared that + * is employed by the create() method. */ + AWSv4ComplMulti(const req_state* const s, + boost::string_view date, + boost::string_view credential_scope, + boost::string_view seed_signature, + const signing_key_t& signing_key) + : io_base_t(nullptr), + cct(s->cct), + date(std::move(date)), + credential_scope(std::move(credential_scope)), + signing_key(signing_key), + + /* The evolving state. */ + chunk_meta(ChunkMeta::create_first(seed_signature)), + stream_pos(0), + sha256_hash(calc_hash_sha256_open_stream()), + prev_chunk_signature(std::move(seed_signature)) { + } + + ~AWSv4ComplMulti() { + if (sha256_hash) { + calc_hash_sha256_close_stream(&sha256_hash); + } + } + + /* rgw::io::DecoratedRestfulClient. */ + size_t recv_body(char* buf, size_t max) override; + + /* rgw::auth::Completer. */ + void modify_request_state(req_state* s_rw) override; + bool complete() override; + + /* Factories. */ + static cmplptr_t create(const req_state* s, + boost::string_view date, + boost::string_view credential_scope, + boost::string_view seed_signature, + const boost::optional& secret_key); + +}; + +class AWSv4ComplSingle : public rgw::auth::Completer, + public rgw::io::DecoratedRestfulClient, + public std::enable_shared_from_this { + using io_base_t = rgw::io::DecoratedRestfulClient; + + CephContext* const cct; + const char* const expected_request_payload_hash; + ceph::crypto::SHA256* sha256_hash = nullptr; + +public: + /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). We need + * the constructor to be public because of the std::make_shared employed by + * the create() method. */ + AWSv4ComplSingle(const req_state* const s); + + ~AWSv4ComplSingle() { + if (sha256_hash) { + calc_hash_sha256_close_stream(&sha256_hash); + } + } + + /* rgw::io::DecoratedRestfulClient. */ + size_t recv_body(char* buf, size_t max) override; + + /* rgw::auth::Completer. */ + void modify_request_state(req_state* s_rw) override; + bool complete() override; + + /* Factories. */ + static cmplptr_t create(const req_state* s, + const boost::optional&); + +}; + +} /* namespace s3 */ +} /* namespace auth */ +} /* namespace rgw */ + +void rgw_create_s3_canonical_header( + const char *method, + const char *content_md5, + const char *content_type, + const char *date, + const std::map& meta_map, + const char *request_uri, + const std::map& sub_resources, + std::string& dest_str); +bool rgw_create_s3_canonical_header(const req_info& info, + utime_t *header_time, /* out */ + std::string& dest, /* out */ + bool qsr); +static inline std::tuple +rgw_create_s3_canonical_header(const req_info& info, const bool qsr) { + std::string dest; + utime_t header_time; + + const bool ok = rgw_create_s3_canonical_header(info, &header_time, dest, qsr); + return std::make_tuple(ok, dest, header_time); +} + +namespace rgw { +namespace auth { +namespace s3 { + +static constexpr char AWS4_HMAC_SHA256_STR[] = "AWS4-HMAC-SHA256"; +static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR[] = "AWS4-HMAC-SHA256-PAYLOAD"; + +static constexpr char AWS4_EMPTY_PAYLOAD_HASH[] = \ + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + +static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD"; + +static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \ + "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"; + +int parse_credentials(const req_info& info, /* in */ + boost::string_view& access_key_id, /* out */ + boost::string_view& credential_scope, /* out */ + boost::string_view& signedheaders, /* out */ + boost::string_view& signature, /* out */ + boost::string_view& date, /* out */ + bool& using_qs); /* out */ + +static inline std::string get_v4_canonical_uri(const req_info& info) { + /* The code should normalize according to RFC 3986 but S3 does NOT do path + * normalization that SigV4 typically does. This code follows the same + * approach that boto library. See auth.py:canonical_uri(...). */ + + std::string canonical_uri = info.request_uri_aws4; + + if (canonical_uri.empty()) { + canonical_uri = "/"; + } else { + boost::replace_all(canonical_uri, "+", "%20"); + } + + return canonical_uri; +} + +static inline const char* get_v4_exp_payload_hash(const req_info& info) +{ + /* In AWSv4 the hash of real, transfered payload IS NOT necessary to form + * a Canonical Request, and thus verify a Signature. x-amz-content-sha256 + * header lets get the information very early -- before seeing first byte + * of HTTP body. As a consequence, we can decouple Signature verification + * from payload's fingerprint check. */ + const char *expected_request_payload_hash = \ + info.env->get("HTTP_X_AMZ_CONTENT_SHA256"); + + if (!expected_request_payload_hash) { + /* An HTTP client MUST send x-amz-content-sha256. The single exception + * is the case of using the Query Parameters where "UNSIGNED-PAYLOAD" + * literals are used for crafting Canonical Request: + * + * You don't include a payload hash in the Canonical Request, because + * when you create a presigned URL, you don't know the payload content + * because the URL is used to upload an arbitrary payload. Instead, you + * use a constant string UNSIGNED-PAYLOAD. */ + expected_request_payload_hash = AWS4_UNSIGNED_PAYLOAD_HASH; + } + + return expected_request_payload_hash; +} + +static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash) +{ + return boost::equals(exp_payload_hash, AWS4_UNSIGNED_PAYLOAD_HASH); +} + +static inline bool is_v4_payload_empty(const req_state* const s) +{ + /* from rfc2616 - 4.3 Message Body + * + * "The presence of a message-body in a request is signaled by the inclusion + * of a Content-Length or Transfer-Encoding header field in the request's + * message-headers." */ + return s->content_length == 0 && + s->info.env->get("HTTP_TRANSFER_ENCODING") == nullptr; +} + +static inline bool is_v4_payload_streamed(const char* const exp_payload_hash) +{ + return boost::equals(exp_payload_hash, AWS4_STREAMING_PAYLOAD_HASH); +} + +std::string get_v4_canonical_qs(const req_info& info, bool using_qs); + +boost::optional +get_v4_canonical_headers(const req_info& info, + const boost::string_view& signedheaders, + bool using_qs, + bool force_boto2_compat); + +extern sha256_digest_t +get_v4_canon_req_hash(CephContext* cct, + const boost::string_view& http_verb, + const std::string& canonical_uri, + const std::string& canonical_qs, + const std::string& canonical_hdrs, + const boost::string_view& signed_hdrs, + const boost::string_view& request_payload_hash); + +AWSEngine::VersionAbstractor::string_to_sign_t +get_v4_string_to_sign(CephContext* cct, + const boost::string_view& algorithm, + const boost::string_view& request_date, + const boost::string_view& credential_scope, + const sha256_digest_t& canonreq_hash); + +extern AWSEngine::VersionAbstractor::server_signature_t +get_v4_signature(const boost::string_view& credential_scope, + CephContext* const cct, + const boost::string_view& secret_key, + const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign); + +extern AWSEngine::VersionAbstractor::server_signature_t +get_v2_signature(CephContext*, + const std::string& secret_key, + const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign); + +} /* namespace s3 */ +} /* namespace auth */ +} /* namespace rgw */ + +#endif