From: Radoslaw Zarzynski Date: Sat, 29 Apr 2017 18:46:03 +0000 (+0200) Subject: rgw: split the AWSv4Completer and clean-up the code. X-Git-Tag: v12.1.0~155^2~24 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=68bc0d0bb6f0cdc1e07ac47d066c9b1077d763ed;p=ceph.git rgw: split the AWSv4Completer and clean-up the code. Signed-off-by: Radoslaw Zarzynski --- diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index f5b5277b25f6..d614de3f44b0 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -811,12 +811,12 @@ std::string get_v4_signature(CephContext* const cct, return signature; } -bool AWSv4Completer::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos) const +bool AWSv4ComplMulti::ChunkMeta::is_new_chunk_in_stream(size_t stream_pos) const { return stream_pos >= (data_offset_in_stream + data_length); } -size_t AWSv4Completer::ChunkMeta::get_data_size(size_t stream_pos) const +size_t AWSv4ComplMulti::ChunkMeta::get_data_size(size_t stream_pos) const { if (stream_pos > (data_offset_in_stream + data_length)) { /* Data in parsing_buf. */ @@ -826,16 +826,20 @@ size_t AWSv4Completer::ChunkMeta::get_data_size(size_t stream_pos) const } } -std::pair -AWSv4Completer::ChunkMeta::create_next(ChunkMeta&& old, - const char* const metabuf, - const size_t metabuf_len) + +/* AWSv4 completers begin. */ +std::pair +AWSv4ComplMulti::ChunkMeta::create_next(CephContext* const cct, + ChunkMeta&& old, + const char* const metabuf, + const size_t metabuf_len) { boost::string_ref metastr(metabuf, metabuf_len); const size_t semicolon_pos = metastr.find(";"); if (semicolon_pos == boost::string_ref::npos) { - dout(20) << "AWSv4Completer cannot find the ';' separator" << dendl; + ldout(cct, 20) << "AWSv4ComplMulti cannot find the ';' separator" + << dendl; throw rgw::io::Exception(EINVAL, std::system_category()); } @@ -843,7 +847,8 @@ AWSv4Completer::ChunkMeta::create_next(ChunkMeta&& old, /* strtoull ignores the "\r\n" sequence after each non-first chunk. */ const size_t data_length = std::strtoull(metabuf, &data_field_end, 16); if (data_length == 0 && data_field_end == metabuf) { - dout(20) << "AWSv4Completer: cannot parse the data size" << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: cannot parse the data size" + << dendl; throw rgw::io::Exception(EINVAL, std::system_category()); } @@ -851,21 +856,24 @@ AWSv4Completer::ChunkMeta::create_next(ChunkMeta&& old, const auto signature_part = metastr.substr(semicolon_pos + 1); const size_t eq_sign_pos = signature_part.find("="); if (eq_sign_pos == boost::string_ref::npos) { - dout(20) << "AWSv4Completer: cannot find the '=' separator" << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: cannot find the '=' separator" + << dendl; throw rgw::io::Exception(EINVAL, std::system_category()); } /* OK, we have at least the beginning of a signature. */ const size_t data_sep_pos = signature_part.find("\r\n"); if (data_sep_pos == boost::string_ref::npos) { - dout(20) << "AWSv4Completer: no new line at signature end" << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: no new line at signature end" + << dendl; throw rgw::io::Exception(EINVAL, std::system_category()); } const auto signature = \ signature_part.substr(eq_sign_pos + 1, data_sep_pos - 1 - eq_sign_pos); if (signature.length() != SIG_SIZE) { - dout(20) << "AWSv4Completer: signature.length() != 64" << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: signature.length() != 64" + << dendl; throw rgw::io::Exception(EINVAL, std::system_category()); } @@ -873,10 +881,10 @@ AWSv4Completer::ChunkMeta::create_next(ChunkMeta&& old, + semicolon_pos + strlen(";") + data_sep_pos + strlen("\r\n") + old.data_offset_in_stream + old.data_length; - dout(20) << "parsed new chunk; signature=" << signature - << ", data_length=" << data_length - << ", data_starts_in_stream=" << data_starts_in_stream - << dendl; + ldout(cct, 20) << "parsed new chunk; signature=" << signature + << ", data_length=" << data_length + << ", data_starts_in_stream=" << data_starts_in_stream + << dendl; return std::make_pair(ChunkMeta(data_starts_in_stream, data_length, @@ -885,30 +893,28 @@ AWSv4Completer::ChunkMeta::create_next(ChunkMeta&& old, } std::string -AWSv4Completer::calc_chunk_signature(const std::string& payload_hash) const +AWSv4ComplMulti::calc_chunk_signature(const std::string& payload_hash) const { - if (!signing_key) { - return std::string(); - } - std::string string_to_sign = "AWS4-HMAC-SHA256-PAYLOAD\n"; string_to_sign.append(date + "\n"); string_to_sign.append(credential_scope + "\n"); - string_to_sign.append(seed_signature + "\n"); + string_to_sign.append(prev_chunk_signature + "\n"); string_to_sign.append(std::string(AWS4_EMPTY_PAYLOAD_HASH) + "\n"); string_to_sign.append(payload_hash); - dout(20) << "AWSv4Completer: string_to_sign=\n" << string_to_sign << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: string_to_sign=\n" << string_to_sign + << dendl; + /* new chunk signature */ - const auto sighex = buf_to_hex(calc_hmac_sha256(*signing_key, + const auto sighex = buf_to_hex(calc_hmac_sha256(signing_key, string_to_sign)); /* FIXME(rzarzynski): std::string here is really unnecessary. */ return std::string(sighex.data(), sighex.size() - 1); } -bool AWSv4Completer::is_signature_mismatched() +bool AWSv4ComplMulti::is_signature_mismatched() { /* The validity of previous chunk can be verified only after getting meta- * data of the next one. */ @@ -916,20 +922,21 @@ bool AWSv4Completer::is_signature_mismatched() const auto calc_signature = calc_chunk_signature(payload_hash); if (chunk_meta.get_signature() != calc_signature) { - dout(20) << "AWSv4Completer: chunk signature mismatch" << dendl; - dout(20) << "AWSv4Completer: declared signature=" - << chunk_meta.get_signature() << dendl; - dout(20) << "AWSv4Completer: calculated signature=" - << calc_signature << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: ERROR: chunk signature mismatch" + << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: declared signature=" + << chunk_meta.get_signature() << dendl; + ldout(cct, 20) << "AWSv4ComplMulti: calculated signature=" + << calc_signature << dendl; return true; } else { - seed_signature = chunk_meta.get_signature(); + prev_chunk_signature = chunk_meta.get_signature(); return false; } } -size_t AWSv4Completer::recv_chunked(char* const buf, const size_t buf_max) +size_t AWSv4ComplMulti::recv_body(char* const buf, const size_t buf_max) { /* Buffer stores only parsed stream. Raw values reflect the stream * we're getting from a client. */ @@ -963,8 +970,8 @@ size_t AWSv4Completer::recv_chunked(char* const buf, const size_t buf_max) size_t consumed; std::tie(chunk_meta, consumed) = \ - ChunkMeta::create_next(std::move(chunk_meta), - parsing_buf.data(), parsing_buf.size()); + ChunkMeta::create_next(cct, std::move(chunk_meta), + parsing_buf.data(), parsing_buf.size()); /* We can drop the bytes consumed during metadata parsing. The remainder * can be chunk's data plus possibly beginning of next chunks' metadata. */ @@ -1008,58 +1015,71 @@ size_t AWSv4Completer::recv_chunked(char* const buf, const size_t buf_max) to_extract -= received; } - dout(20) << "AWSv4Completer: filled=" << buf_pos << dendl; + dout(20) << "AWSv4ComplMulti: filled=" << buf_pos << dendl; return buf_pos; } -size_t AWSv4Completer::recv_body(char* const buf, const size_t max) +void AWSv4ComplMulti::modify_request_state(req_state* const s_rw) { - if (aws4_auth_streaming_mode) { - return recv_chunked(buf, max); - } - - const auto received = io_base_t::recv_body(buf, max); + /* Install the filter over rgw::io::RestfulClient. */ + AWS_AUTHv4_IO(s_rw)->add_filter( + std::static_pointer_cast(shared_from_this())); +} - if (sha256_hash) { - calc_hash_sha256_update_stream(sha256_hash, buf, received); +bool AWSv4ComplMulti::complete() +{ + /* Now it's time to verify the signature of the last, zero-length chunk. */ + if (is_signature_mismatched()) { + ldout(cct, 10) << "ERROR: signature of last chunk does not match" + << dendl; + return false; + } else { + return true; } - - return received; } -void AWSv4Completer::modify_request_state(req_state* const s_rw) +rgw::auth::Completer::cmplptr_t +AWSv4ComplMulti::create(const req_state* const s, + std::string date, + std::string credential_scope, + std::string seed_signature, + const boost::optional& secret_key) { - if (!signing_key && aws4_auth_streaming_mode) { + if (!secret_key) { /* Some external authorizers (like Keystone) aren't fully compliant with * AWSv4. They do not provide the secret_key which is necessary to handle * the streamed upload. */ throw -ERR_NOT_IMPLEMENTED; } - /* Install the filter over rgw::io::RestfulClient. */ - AWS_AUTHv4_IO(s_rw)->add_filter( - std::static_pointer_cast(shared_from_this())); + const auto signing_key = \ + rgw::auth::s3::get_v4_signing_key(s->cct, credential_scope, *secret_key); + + return rgw::auth::Completer::cmplptr_t( + new AWSv4ComplMulti(s, + std::move(date), + std::move(credential_scope), + std::move(seed_signature), + signing_key)); } -bool AWSv4Completer::complete() +size_t AWSv4ComplSingle::recv_body(char* const buf, const size_t max) { - /* Now it's time to verify the signature of the last, zero-length chunk. */ - if (aws4_auth_streaming_mode && is_signature_mismatched()) { - return false; - } + const auto received = io_base_t::recv_body(buf, max); + calc_hash_sha256_update_stream(sha256_hash, buf, received); - if (!aws4_auth_needs_complete) { - return true; - } + return received; +} - /* 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 = \ - rgw::auth::s3::get_v4_exp_payload_hash(s->info); +void AWSv4ComplSingle::modify_request_state(req_state* const s_rw) +{ + /* Install the filter over rgw::io::RestfulClient. */ + AWS_AUTHv4_IO(s_rw)->add_filter( + std::static_pointer_cast(shared_from_this())); +} +bool AWSv4ComplSingle::complete() +{ /* The completer is only for the cases where signed payload has been * requested. It won't be used, for instance, during the query string-based * authentication. */ @@ -1069,43 +1089,28 @@ bool AWSv4Completer::complete() if (payload_hash.compare(expected_request_payload_hash) == 0) { return true; } else { - ldout(s->cct, 10) << "ERROR: x-amz-content-sha256 does not match" - << dendl; - ldout(s->cct, 10) << "ERROR: grab_aws4_sha256_hash()=" - << payload_hash << dendl; - ldout(s->cct, 10) << "ERROR: expected_request_payload_hash=" - << expected_request_payload_hash << dendl; + ldout(cct, 10) << "ERROR: x-amz-content-sha256 does not match" + << dendl; + ldout(cct, 10) << "ERROR: grab_aws4_sha256_hash()=" + << payload_hash << dendl; + ldout(cct, 10) << "ERROR: expected_request_payload_hash=" + << expected_request_payload_hash << dendl; return false; } } -rgw::auth::Completer::cmplptr_t -AWSv4Completer::create_for_single_chunk(const req_state* const s, - const boost::optional&) -{ - return rgw::auth::Completer::cmplptr_t(new AWSv4Completer(s)); +AWSv4ComplSingle::AWSv4ComplSingle(const req_state* const s) + : io_base_t(nullptr), + cct(s->cct), + expected_request_payload_hash(get_v4_exp_payload_hash(s->info)), + sha256_hash(calc_hash_sha256_open_stream()) { } rgw::auth::Completer::cmplptr_t -AWSv4Completer::create_for_multi_chunk(const req_state* const s, - std::string date, - std::string credential_scope, - std::string seed_signature, - const boost::optional& secret_key) +AWSv4ComplSingle::create(const req_state* const s, + const boost::optional&) { - boost::optional> signing_key; - if (secret_key) { - signing_key = rgw::auth::s3::get_v4_signing_key(s->cct, credential_scope, - *secret_key); - } - - return rgw::auth::Completer::cmplptr_t( - new AWSv4Completer(s, - std::move(date), - std::move(credential_scope), - std::move(seed_signature), - signing_key)); + return rgw::auth::Completer::cmplptr_t(new AWSv4ComplSingle(s)); } } /* namespace s3 */ diff --git a/src/rgw/rgw_auth_s3.h b/src/rgw/rgw_auth_s3.h index 4e61a6be5fde..4ea23eb0748d 100644 --- a/src/rgw/rgw_auth_s3.h +++ b/src/rgw/rgw_auth_s3.h @@ -130,13 +130,20 @@ public: }; -/* TODO(rzarzynski): decompose the AWSv4Completer into two separate completers: - * one for single chunk mode and second for streaming mode. */ -class AWSv4Completer : public rgw::auth::Completer, - public rgw::io::DecoratedRestfulClient, - public std::enable_shared_from_this { -private: +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; + + /* TODO(rzarzynski): move to boost::string_ref. This should be just fine + * as (all?) parameters here are actually views over req_info. */ + const std::string date; + const std::string credential_scope; + const signing_key_t signing_key; class ChunkMeta { size_t data_offset_in_stream = 0; @@ -185,77 +192,90 @@ private: /* 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(ChunkMeta&& prev, const char* metabuf, size_t metabuf_len); + 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; - SHA256* sha256_hash = nullptr; - - size_t stream_pos = 0; - const bool aws4_auth_needs_complete = true; - const bool aws4_auth_streaming_mode = false; - - /* TODO(rzarzynski): move to boost::string_ref. This should be just fine - * as (all?) parameters here are actually views over req_info. */ - std::string date; - std::string credential_scope; - std::string seed_signature; - boost::optional> signing_key; - - /* TODO(rzarzynski): this won't be necessary after moving to filter-over- - * rgw::io::RestfulClient. */ - const req_state* const s; - - using signing_key_t = boost::optional>; - AWSv4Completer(const req_state* const s, - std::string date, - std::string credential_scope, - std::string seed_signature, - const signing_key_t& signing_key) + SHA256* sha256_hash; + std::string prev_chunk_signature; + + AWSv4ComplMulti(const req_state* const s, + std::string date, + std::string credential_scope, + std::string seed_signature, + const signing_key_t& signing_key) : io_base_t(nullptr), - chunk_meta(ChunkMeta::create_first(seed_signature)), - sha256_hash(calc_hash_sha256_open_stream()), - aws4_auth_needs_complete(false), - aws4_auth_streaming_mode(true), + cct(s->cct), date(std::move(date)), credential_scope(std::move(credential_scope)), - seed_signature(std::move(seed_signature)), signing_key(signing_key), - s(s) { - } - AWSv4Completer(const req_state* const s) - : io_base_t(nullptr), + /* The evolving state. */ + chunk_meta(ChunkMeta::create_first(seed_signature)), + stream_pos(0), sha256_hash(calc_hash_sha256_open_stream()), - s(s) { + prev_chunk_signature(std::move(seed_signature)) { } bool is_signature_mismatched(); std::string calc_chunk_signature(const std::string& payload_hash) const; - size_t recv_chunked(char* buf, size_t max); public: + ~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) override; + void modify_request_state(req_state* s_rw) override; bool complete() override; /* Factories. */ - static cmplptr_t - create_for_single_chunk(const req_state* s, - const boost::optional&); + static cmplptr_t create(const req_state* s, + std::string date, + std::string credential_scope, + std::string seed_signature, + const boost::optional& secret_key); - static cmplptr_t - create_for_multi_chunk(const req_state* s, - std::string date, - std::string credential_scope, - std::string 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; + + /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). */ + AWSv4ComplSingle(const req_state* const s); + +public: + ~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&); }; diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 47a96c215ff6..7782ab15a3b8 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -3780,7 +3780,7 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s, throw -ERR_NOT_IMPLEMENTED; } - const auto cmpl_factory = std::bind(AWSv4Completer::create_for_single_chunk, + const auto cmpl_factory = std::bind(AWSv4ComplSingle::create, s, std::placeholders::_1); return std::make_tuple(std::move(access_key_id), @@ -3817,7 +3817,7 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s, /* In the case of query string-based authentication there should be no * x-amz-content-sha256 header and the value "UNSIGNED-PAYLOAD" is used * for CanonReq. */ - const auto cmpl_factory = std::bind(AWSv4Completer::create_for_multi_chunk, + const auto cmpl_factory = std::bind(AWSv4ComplMulti::create, s, std::move(date), std::move(credential_scope),