]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include "librbd/migration/S3Stream.h" | |
5 | #include "common/armor.h" | |
6 | #include "common/ceph_crypto.h" | |
7 | #include "common/ceph_time.h" | |
8 | #include "common/dout.h" | |
9 | #include "common/errno.h" | |
10 | #include "librbd/AsioEngine.h" | |
11 | #include "librbd/ImageCtx.h" | |
12 | #include "librbd/Utils.h" | |
13 | #include "librbd/asio/Utils.h" | |
14 | #include "librbd/io/AioCompletion.h" | |
15 | #include "librbd/io/ReadResult.h" | |
16 | #include "librbd/migration/HttpClient.h" | |
17 | #include "librbd/migration/HttpProcessorInterface.h" | |
18 | #include <boost/beast/http.hpp> | |
19 | ||
20 | #undef FMT_HEADER_ONLY | |
21 | #define FMT_HEADER_ONLY 1 | |
22 | #include <fmt/chrono.h> | |
23 | #include <fmt/format.h> | |
24 | ||
25 | #include <time.h> | |
26 | ||
27 | namespace librbd { | |
28 | namespace migration { | |
29 | ||
30 | using HttpRequest = boost::beast::http::request<boost::beast::http::empty_body>; | |
31 | ||
32 | namespace { | |
33 | ||
34 | const std::string URL_KEY {"url"}; | |
35 | const std::string ACCESS_KEY {"access_key"}; | |
36 | const std::string SECRET_KEY {"secret_key"}; | |
37 | ||
38 | } // anonymous namespace | |
39 | ||
40 | template <typename I> | |
41 | struct S3Stream<I>::HttpProcessor : public HttpProcessorInterface { | |
42 | S3Stream* s3stream; | |
43 | ||
44 | HttpProcessor(S3Stream* s3stream) : s3stream(s3stream) { | |
45 | } | |
46 | ||
47 | void process_request(EmptyRequest& request) override { | |
48 | s3stream->process_request(request); | |
49 | } | |
50 | }; | |
51 | ||
52 | #define dout_subsys ceph_subsys_rbd | |
53 | #undef dout_prefix | |
54 | #define dout_prefix *_dout << "librbd::migration::S3Stream: " << this \ | |
55 | << " " << __func__ << ": " | |
56 | ||
57 | template <typename I> | |
58 | S3Stream<I>::S3Stream(I* image_ctx, const json_spirit::mObject& json_object) | |
59 | : m_image_ctx(image_ctx), m_cct(image_ctx->cct), | |
60 | m_asio_engine(image_ctx->asio_engine), m_json_object(json_object), | |
61 | m_http_processor(std::make_unique<HttpProcessor>(this)) { | |
62 | } | |
63 | ||
64 | template <typename I> | |
65 | S3Stream<I>::~S3Stream() { | |
66 | } | |
67 | ||
68 | template <typename I> | |
69 | void S3Stream<I>::open(Context* on_finish) { | |
70 | auto& url_value = m_json_object[URL_KEY]; | |
71 | if (url_value.type() != json_spirit::str_type) { | |
72 | lderr(m_cct) << "failed to locate '" << URL_KEY << "' key" << dendl; | |
73 | on_finish->complete(-EINVAL); | |
74 | return; | |
75 | } | |
76 | ||
77 | auto& access_key = m_json_object[ACCESS_KEY]; | |
78 | if (access_key.type() != json_spirit::str_type) { | |
79 | lderr(m_cct) << "failed to locate '" << ACCESS_KEY << "' key" << dendl; | |
80 | on_finish->complete(-EINVAL); | |
81 | return; | |
82 | } | |
83 | ||
84 | auto& secret_key = m_json_object[SECRET_KEY]; | |
85 | if (secret_key.type() != json_spirit::str_type) { | |
86 | lderr(m_cct) << "failed to locate '" << SECRET_KEY << "' key" << dendl; | |
87 | on_finish->complete(-EINVAL); | |
88 | return; | |
89 | } | |
90 | ||
91 | m_url = url_value.get_str(); | |
92 | ||
93 | librados::Rados rados(m_image_ctx->md_ctx); | |
94 | int r = 0; | |
95 | m_access_key = access_key.get_str(); | |
96 | if (util::is_config_key_uri(m_access_key)) { | |
97 | r = util::get_config_key(rados, m_access_key, &m_access_key); | |
98 | if (r < 0) { | |
99 | lderr(m_cct) << "failed to retrieve access key from config: " | |
100 | << cpp_strerror(r) << dendl; | |
101 | on_finish->complete(r); | |
102 | return; | |
103 | } | |
104 | } | |
105 | ||
106 | m_secret_key = secret_key.get_str(); | |
107 | if (util::is_config_key_uri(m_secret_key)) { | |
108 | r = util::get_config_key(rados, m_secret_key, &m_secret_key); | |
109 | if (r < 0) { | |
110 | lderr(m_cct) << "failed to retrieve secret key from config: " | |
111 | << cpp_strerror(r) << dendl; | |
112 | on_finish->complete(r); | |
113 | return; | |
114 | } | |
115 | } | |
116 | ||
117 | ldout(m_cct, 10) << "url=" << m_url << ", " | |
118 | << "access_key=" << m_access_key << dendl; | |
119 | ||
120 | m_http_client.reset(HttpClient<I>::create(m_image_ctx, m_url)); | |
121 | m_http_client->set_http_processor(m_http_processor.get()); | |
122 | m_http_client->open(on_finish); | |
123 | } | |
124 | ||
125 | template <typename I> | |
126 | void S3Stream<I>::close(Context* on_finish) { | |
127 | ldout(m_cct, 10) << dendl; | |
128 | ||
129 | if (!m_http_client) { | |
130 | on_finish->complete(0); | |
131 | return; | |
132 | } | |
133 | ||
134 | m_http_client->close(on_finish); | |
135 | } | |
136 | ||
137 | template <typename I> | |
138 | void S3Stream<I>::get_size(uint64_t* size, Context* on_finish) { | |
139 | ldout(m_cct, 10) << dendl; | |
140 | ||
141 | m_http_client->get_size(size, on_finish); | |
142 | } | |
143 | ||
144 | template <typename I> | |
145 | void S3Stream<I>::read(io::Extents&& byte_extents, bufferlist* data, | |
146 | Context* on_finish) { | |
147 | ldout(m_cct, 20) << "byte_extents=" << byte_extents << dendl; | |
148 | ||
149 | m_http_client->read(std::move(byte_extents), data, on_finish); | |
150 | } | |
151 | ||
152 | template <typename I> | |
153 | void S3Stream<I>::process_request(HttpRequest& http_request) { | |
154 | ldout(m_cct, 20) << dendl; | |
155 | ||
156 | // format RFC 1123 date/time | |
157 | auto time = ceph::real_clock::to_time_t(ceph::real_clock::now()); | |
158 | struct tm timeInfo; | |
159 | gmtime_r(&time, &timeInfo); | |
160 | ||
161 | std::string date = fmt::format("{:%a, %d %b %Y %H:%M:%S %z}", timeInfo); | |
162 | http_request.set(boost::beast::http::field::date, date); | |
163 | ||
164 | // note: we don't support S3 subresources | |
165 | std::string canonicalized_resource = std::string(http_request.target()); | |
166 | ||
167 | std::string string_to_sign = fmt::format( | |
168 | "{}\n\n\n{}\n{}", | |
169 | std::string(boost::beast::http::to_string(http_request.method())), | |
170 | date, canonicalized_resource); | |
171 | ||
172 | // create HMAC-SHA1 signature from secret key + string-to-sign | |
173 | sha1_digest_t digest; | |
174 | ceph::crypto::HMACSHA1 hmac( | |
175 | reinterpret_cast<const unsigned char*>(m_secret_key.data()), | |
176 | m_secret_key.size()); | |
177 | hmac.Update(reinterpret_cast<const unsigned char*>(string_to_sign.data()), | |
178 | string_to_sign.size()); | |
179 | hmac.Final(reinterpret_cast<unsigned char*>(digest.v)); | |
180 | ||
181 | // base64 encode the result | |
182 | char buf[64]; | |
183 | int r = ceph_armor(std::begin(buf), std::begin(buf) + sizeof(buf), | |
184 | reinterpret_cast<const char *>(digest.v), | |
185 | reinterpret_cast<const char *>(digest.v + digest.SIZE)); | |
186 | if (r < 0) { | |
187 | ceph_abort("ceph_armor failed"); | |
188 | } | |
189 | ||
190 | // store the access-key + signature in the HTTP authorization header | |
191 | std::string signature = std::string(std::begin(buf), std::begin(buf) + r); | |
192 | std::string authorization = fmt::format("AWS {}:{}", m_access_key, signature); | |
193 | http_request.set(boost::beast::http::field::authorization, authorization); | |
194 | ||
195 | ldout(m_cct, 20) << "string_to_sign=" << string_to_sign << ", " | |
196 | << "authorization=" << authorization << dendl; | |
197 | } | |
198 | ||
199 | } // namespace migration | |
200 | } // namespace librbd | |
201 | ||
202 | template class librbd::migration::S3Stream<librbd::ImageCtx>; |