]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #ifndef CEPH_RGW_AUTH_S3_H | |
5 | #define CEPH_RGW_AUTH_S3_H | |
6 | ||
31f18b77 FG |
7 | #include <array> |
8 | #include <memory> | |
7c673cae FG |
9 | #include <string> |
10 | #include <tuple> | |
11 | ||
31f18b77 FG |
12 | #include <boost/algorithm/string.hpp> |
13 | #include <boost/container/static_vector.hpp> | |
14 | #include <boost/utility/string_ref.hpp> | |
15 | #include <boost/utility/string_view.hpp> | |
16 | ||
17 | #include "common/sstring.hh" | |
7c673cae FG |
18 | #include "rgw_common.h" |
19 | #include "rgw_rest_s3.h" | |
20 | ||
21 | #include "rgw_auth.h" | |
22 | #include "rgw_auth_filters.h" | |
23 | #include "rgw_auth_keystone.h" | |
24 | ||
25 | ||
26 | namespace rgw { | |
27 | namespace auth { | |
28 | namespace s3 { | |
29 | ||
94b18763 FG |
30 | static constexpr auto RGW_AUTH_GRACE = std::chrono::minutes{15}; |
31 | ||
32 | // returns true if the request time is within RGW_AUTH_GRACE of the current time | |
33 | bool is_time_skew_ok(time_t t); | |
34 | ||
7c673cae FG |
35 | class ExternalAuthStrategy : public rgw::auth::Strategy, |
36 | public rgw::auth::RemoteApplier::Factory { | |
37 | typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; | |
38 | RGWRados* const store; | |
28e407b8 | 39 | rgw::auth::ImplicitTenants& implicit_tenant_context; |
7c673cae FG |
40 | |
41 | using keystone_config_t = rgw::keystone::CephCtxConfig; | |
42 | using keystone_cache_t = rgw::keystone::TokenCache; | |
43 | using EC2Engine = rgw::auth::keystone::EC2Engine; | |
44 | ||
3efd9988 | 45 | boost::optional <EC2Engine> keystone_engine; |
7c673cae FG |
46 | LDAPEngine ldap_engine; |
47 | ||
48 | aplptr_t create_apl_remote(CephContext* const cct, | |
49 | const req_state* const s, | |
50 | rgw::auth::RemoteApplier::acl_strategy_t&& acl_alg, | |
51 | const rgw::auth::RemoteApplier::AuthInfo info | |
52 | ) const override { | |
53 | auto apl = rgw::auth::add_sysreq(cct, store, s, | |
54 | rgw::auth::RemoteApplier(cct, store, std::move(acl_alg), info, | |
28e407b8 AA |
55 | implicit_tenant_context, |
56 | rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3)); | |
7c673cae FG |
57 | /* TODO(rzarzynski): replace with static_ptr. */ |
58 | return aplptr_t(new decltype(apl)(std::move(apl))); | |
59 | } | |
60 | ||
61 | public: | |
62 | ExternalAuthStrategy(CephContext* const cct, | |
63 | RGWRados* const store, | |
28e407b8 | 64 | rgw::auth::ImplicitTenants& implicit_tenant_context, |
31f18b77 | 65 | AWSEngine::VersionAbstractor* const ver_abstractor) |
7c673cae | 66 | : store(store), |
28e407b8 | 67 | implicit_tenant_context(implicit_tenant_context), |
31f18b77 | 68 | ldap_engine(cct, store, *ver_abstractor, |
7c673cae FG |
69 | static_cast<rgw::auth::RemoteApplier::Factory*>(this)) { |
70 | ||
71 | if (cct->_conf->rgw_s3_auth_use_keystone && | |
72 | ! cct->_conf->rgw_keystone_url.empty()) { | |
3efd9988 FG |
73 | |
74 | keystone_engine.emplace(cct, ver_abstractor, | |
75 | static_cast<rgw::auth::RemoteApplier::Factory*>(this), | |
76 | keystone_config_t::get_instance(), | |
77 | keystone_cache_t::get_instance<keystone_config_t>()); | |
78 | add_engine(Control::SUFFICIENT, *keystone_engine); | |
79 | ||
7c673cae FG |
80 | } |
81 | ||
82 | if (cct->_conf->rgw_s3_auth_use_ldap && | |
83 | ! cct->_conf->rgw_ldap_uri.empty()) { | |
84 | add_engine(Control::SUFFICIENT, ldap_engine); | |
85 | } | |
86 | } | |
87 | ||
88 | const char* get_name() const noexcept override { | |
89 | return "rgw::auth::s3::AWSv2ExternalAuthStrategy"; | |
90 | } | |
91 | }; | |
92 | ||
93 | ||
d2e6a577 FG |
94 | template <class AbstractorT, |
95 | bool AllowAnonAccessT = false> | |
31f18b77 FG |
96 | class AWSAuthStrategy : public rgw::auth::Strategy, |
97 | public rgw::auth::LocalApplier::Factory { | |
7c673cae FG |
98 | typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; |
99 | ||
31f18b77 FG |
100 | static_assert(std::is_base_of<rgw::auth::s3::AWSEngine::VersionAbstractor, |
101 | AbstractorT>::value, | |
102 | "AbstractorT must be a subclass of rgw::auth::s3::VersionAbstractor"); | |
7c673cae FG |
103 | |
104 | RGWRados* const store; | |
31f18b77 | 105 | AbstractorT ver_abstractor; |
7c673cae | 106 | |
d2e6a577 | 107 | S3AnonymousEngine anonymous_engine; |
7c673cae | 108 | ExternalAuthStrategy external_engines; |
31f18b77 | 109 | LocalEngine local_engine; |
7c673cae FG |
110 | |
111 | aplptr_t create_apl_local(CephContext* const cct, | |
112 | const req_state* const s, | |
113 | const RGWUserInfo& user_info, | |
114 | const std::string& subuser) const override { | |
115 | auto apl = rgw::auth::add_sysreq(cct, store, s, | |
116 | rgw::auth::LocalApplier(cct, user_info, subuser)); | |
117 | /* TODO(rzarzynski): replace with static_ptr. */ | |
118 | return aplptr_t(new decltype(apl)(std::move(apl))); | |
119 | } | |
120 | ||
121 | public: | |
31f18b77 | 122 | AWSAuthStrategy(CephContext* const cct, |
28e407b8 | 123 | rgw::auth::ImplicitTenants& implicit_tenant_context, |
31f18b77 | 124 | RGWRados* const store) |
7c673cae | 125 | : store(store), |
31f18b77 | 126 | ver_abstractor(cct), |
d2e6a577 FG |
127 | anonymous_engine(cct, |
128 | static_cast<rgw::auth::LocalApplier::Factory*>(this)), | |
28e407b8 | 129 | external_engines(cct, store, implicit_tenant_context, &ver_abstractor), |
31f18b77 | 130 | local_engine(cct, store, ver_abstractor, |
7c673cae | 131 | static_cast<rgw::auth::LocalApplier::Factory*>(this)) { |
d2e6a577 FG |
132 | /* The anynoymous auth. */ |
133 | if (AllowAnonAccessT) { | |
134 | add_engine(Control::SUFFICIENT, anonymous_engine); | |
135 | } | |
7c673cae | 136 | |
d2e6a577 | 137 | /* The external auth. */ |
7c673cae FG |
138 | Control local_engine_mode; |
139 | if (! external_engines.is_empty()) { | |
140 | add_engine(Control::SUFFICIENT, external_engines); | |
141 | ||
142 | local_engine_mode = Control::FALLBACK; | |
143 | } else { | |
144 | local_engine_mode = Control::SUFFICIENT; | |
145 | } | |
146 | ||
d2e6a577 | 147 | /* The local auth. */ |
7c673cae FG |
148 | if (cct->_conf->rgw_s3_auth_use_rados) { |
149 | add_engine(local_engine_mode, local_engine); | |
150 | } | |
151 | } | |
152 | ||
153 | const char* get_name() const noexcept override { | |
31f18b77 | 154 | return "rgw::auth::s3::AWSAuthStrategy"; |
7c673cae FG |
155 | } |
156 | }; | |
157 | ||
31f18b77 FG |
158 | |
159 | class AWSv4ComplMulti : public rgw::auth::Completer, | |
160 | public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>, | |
161 | public std::enable_shared_from_this<AWSv4ComplMulti> { | |
162 | using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>; | |
163 | using signing_key_t = std::array<unsigned char, | |
164 | CEPH_CRYPTO_HMACSHA256_DIGESTSIZE>; | |
165 | ||
166 | CephContext* const cct; | |
167 | ||
168 | const boost::string_view date; | |
169 | const boost::string_view credential_scope; | |
170 | const signing_key_t signing_key; | |
171 | ||
172 | class ChunkMeta { | |
173 | size_t data_offset_in_stream = 0; | |
174 | size_t data_length = 0; | |
175 | std::string signature; | |
176 | ||
177 | ChunkMeta(const size_t data_starts_in_stream, | |
178 | const size_t data_length, | |
179 | const boost::string_ref signature) | |
180 | : data_offset_in_stream(data_starts_in_stream), | |
181 | data_length(data_length), | |
182 | signature(signature.to_string()) { | |
183 | } | |
184 | ||
185 | ChunkMeta(const boost::string_view& signature) | |
186 | : signature(signature.to_string()) { | |
187 | } | |
188 | ||
189 | public: | |
190 | static constexpr size_t SIG_SIZE = 64; | |
191 | ||
192 | /* Let's suppose the data length fields can't exceed uint64_t. */ | |
193 | static constexpr size_t META_MAX_SIZE = \ | |
194 | sarrlen("\r\nffffffffffffffff;chunk-signature=") + SIG_SIZE + sarrlen("\r\n"); | |
195 | ||
196 | /* The metadata size of for the last, empty chunk. */ | |
197 | static constexpr size_t META_MIN_SIZE = \ | |
198 | sarrlen("0;chunk-signature=") + SIG_SIZE + sarrlen("\r\n"); | |
199 | ||
200 | /* Detect whether a given stream_pos fits in boundaries of a chunk. */ | |
201 | bool is_new_chunk_in_stream(size_t stream_pos) const; | |
202 | ||
203 | /* Get the remaining data size. */ | |
204 | size_t get_data_size(size_t stream_pos) const; | |
205 | ||
206 | const std::string& get_signature() const { | |
207 | return signature; | |
208 | } | |
209 | ||
210 | /* Factory: create an object representing metadata of first, initial chunk | |
211 | * in a stream. */ | |
212 | static ChunkMeta create_first(const boost::string_view& seed_signature) { | |
213 | return ChunkMeta(seed_signature); | |
214 | } | |
215 | ||
216 | /* Factory: parse a block of META_MAX_SIZE bytes and creates an object | |
217 | * representing non-first chunk in a stream. As the process is sequential | |
218 | * and depends on the previous chunk, caller must pass it. */ | |
219 | static std::pair<ChunkMeta, size_t> create_next(CephContext* cct, | |
220 | ChunkMeta&& prev, | |
221 | const char* metabuf, | |
222 | size_t metabuf_len); | |
223 | } chunk_meta; | |
224 | ||
225 | size_t stream_pos; | |
226 | boost::container::static_vector<char, ChunkMeta::META_MAX_SIZE> parsing_buf; | |
227 | ceph::crypto::SHA256* sha256_hash; | |
228 | std::string prev_chunk_signature; | |
229 | ||
230 | bool is_signature_mismatched(); | |
231 | std::string calc_chunk_signature(const std::string& payload_hash) const; | |
232 | ||
233 | public: | |
234 | /* We need the constructor to be public because of the std::make_shared that | |
235 | * is employed by the create() method. */ | |
236 | AWSv4ComplMulti(const req_state* const s, | |
237 | boost::string_view date, | |
238 | boost::string_view credential_scope, | |
239 | boost::string_view seed_signature, | |
240 | const signing_key_t& signing_key) | |
241 | : io_base_t(nullptr), | |
242 | cct(s->cct), | |
243 | date(std::move(date)), | |
244 | credential_scope(std::move(credential_scope)), | |
245 | signing_key(signing_key), | |
246 | ||
247 | /* The evolving state. */ | |
248 | chunk_meta(ChunkMeta::create_first(seed_signature)), | |
249 | stream_pos(0), | |
250 | sha256_hash(calc_hash_sha256_open_stream()), | |
251 | prev_chunk_signature(std::move(seed_signature)) { | |
252 | } | |
253 | ||
254 | ~AWSv4ComplMulti() { | |
255 | if (sha256_hash) { | |
256 | calc_hash_sha256_close_stream(&sha256_hash); | |
257 | } | |
258 | } | |
259 | ||
260 | /* rgw::io::DecoratedRestfulClient. */ | |
261 | size_t recv_body(char* buf, size_t max) override; | |
262 | ||
263 | /* rgw::auth::Completer. */ | |
264 | void modify_request_state(req_state* s_rw) override; | |
265 | bool complete() override; | |
266 | ||
267 | /* Factories. */ | |
268 | static cmplptr_t create(const req_state* s, | |
269 | boost::string_view date, | |
270 | boost::string_view credential_scope, | |
271 | boost::string_view seed_signature, | |
272 | const boost::optional<std::string>& secret_key); | |
273 | ||
274 | }; | |
275 | ||
276 | class AWSv4ComplSingle : public rgw::auth::Completer, | |
277 | public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>, | |
278 | public std::enable_shared_from_this<AWSv4ComplSingle> { | |
279 | using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>; | |
280 | ||
281 | CephContext* const cct; | |
282 | const char* const expected_request_payload_hash; | |
283 | ceph::crypto::SHA256* sha256_hash = nullptr; | |
284 | ||
285 | public: | |
286 | /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). We need | |
287 | * the constructor to be public because of the std::make_shared employed by | |
288 | * the create() method. */ | |
289 | AWSv4ComplSingle(const req_state* const s); | |
290 | ||
291 | ~AWSv4ComplSingle() { | |
292 | if (sha256_hash) { | |
293 | calc_hash_sha256_close_stream(&sha256_hash); | |
294 | } | |
295 | } | |
296 | ||
297 | /* rgw::io::DecoratedRestfulClient. */ | |
298 | size_t recv_body(char* buf, size_t max) override; | |
299 | ||
300 | /* rgw::auth::Completer. */ | |
301 | void modify_request_state(req_state* s_rw) override; | |
302 | bool complete() override; | |
303 | ||
304 | /* Factories. */ | |
305 | static cmplptr_t create(const req_state* s, | |
306 | const boost::optional<std::string>&); | |
307 | ||
308 | }; | |
309 | ||
7c673cae FG |
310 | } /* namespace s3 */ |
311 | } /* namespace auth */ | |
312 | } /* namespace rgw */ | |
313 | ||
314 | void rgw_create_s3_canonical_header( | |
315 | const char *method, | |
316 | const char *content_md5, | |
317 | const char *content_type, | |
318 | const char *date, | |
319 | const std::map<std::string, std::string>& meta_map, | |
320 | const char *request_uri, | |
321 | const std::map<std::string, std::string>& sub_resources, | |
322 | std::string& dest_str); | |
323 | bool rgw_create_s3_canonical_header(const req_info& info, | |
324 | utime_t *header_time, /* out */ | |
325 | std::string& dest, /* out */ | |
326 | bool qsr); | |
327 | static inline std::tuple<bool, std::string, utime_t> | |
328 | rgw_create_s3_canonical_header(const req_info& info, const bool qsr) { | |
329 | std::string dest; | |
330 | utime_t header_time; | |
331 | ||
332 | const bool ok = rgw_create_s3_canonical_header(info, &header_time, dest, qsr); | |
333 | return std::make_tuple(ok, dest, header_time); | |
334 | } | |
335 | ||
31f18b77 FG |
336 | namespace rgw { |
337 | namespace auth { | |
338 | namespace s3 { | |
339 | ||
340 | static constexpr char AWS4_HMAC_SHA256_STR[] = "AWS4-HMAC-SHA256"; | |
224ce89b | 341 | static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR[] = "AWS4-HMAC-SHA256-PAYLOAD"; |
31f18b77 FG |
342 | |
343 | static constexpr char AWS4_EMPTY_PAYLOAD_HASH[] = \ | |
344 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; | |
345 | ||
346 | static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD"; | |
347 | ||
348 | static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \ | |
349 | "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"; | |
350 | ||
351 | int parse_credentials(const req_info& info, /* in */ | |
352 | boost::string_view& access_key_id, /* out */ | |
353 | boost::string_view& credential_scope, /* out */ | |
354 | boost::string_view& signedheaders, /* out */ | |
355 | boost::string_view& signature, /* out */ | |
356 | boost::string_view& date, /* out */ | |
357 | bool& using_qs); /* out */ | |
358 | ||
359 | static inline std::string get_v4_canonical_uri(const req_info& info) { | |
360 | /* The code should normalize according to RFC 3986 but S3 does NOT do path | |
361 | * normalization that SigV4 typically does. This code follows the same | |
362 | * approach that boto library. See auth.py:canonical_uri(...). */ | |
363 | ||
364 | std::string canonical_uri = info.request_uri_aws4; | |
365 | ||
366 | if (canonical_uri.empty()) { | |
367 | canonical_uri = "/"; | |
368 | } else { | |
369 | boost::replace_all(canonical_uri, "+", "%20"); | |
370 | } | |
371 | ||
372 | return canonical_uri; | |
373 | } | |
374 | ||
375 | static inline const char* get_v4_exp_payload_hash(const req_info& info) | |
376 | { | |
377 | /* In AWSv4 the hash of real, transfered payload IS NOT necessary to form | |
378 | * a Canonical Request, and thus verify a Signature. x-amz-content-sha256 | |
379 | * header lets get the information very early -- before seeing first byte | |
380 | * of HTTP body. As a consequence, we can decouple Signature verification | |
381 | * from payload's fingerprint check. */ | |
382 | const char *expected_request_payload_hash = \ | |
383 | info.env->get("HTTP_X_AMZ_CONTENT_SHA256"); | |
384 | ||
385 | if (!expected_request_payload_hash) { | |
386 | /* An HTTP client MUST send x-amz-content-sha256. The single exception | |
387 | * is the case of using the Query Parameters where "UNSIGNED-PAYLOAD" | |
388 | * literals are used for crafting Canonical Request: | |
389 | * | |
390 | * You don't include a payload hash in the Canonical Request, because | |
391 | * when you create a presigned URL, you don't know the payload content | |
392 | * because the URL is used to upload an arbitrary payload. Instead, you | |
393 | * use a constant string UNSIGNED-PAYLOAD. */ | |
394 | expected_request_payload_hash = AWS4_UNSIGNED_PAYLOAD_HASH; | |
395 | } | |
396 | ||
397 | return expected_request_payload_hash; | |
398 | } | |
399 | ||
400 | static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash) | |
401 | { | |
402 | return boost::equals(exp_payload_hash, AWS4_UNSIGNED_PAYLOAD_HASH); | |
403 | } | |
404 | ||
405 | static inline bool is_v4_payload_empty(const req_state* const s) | |
406 | { | |
407 | /* from rfc2616 - 4.3 Message Body | |
408 | * | |
409 | * "The presence of a message-body in a request is signaled by the inclusion | |
410 | * of a Content-Length or Transfer-Encoding header field in the request's | |
411 | * message-headers." */ | |
412 | return s->content_length == 0 && | |
413 | s->info.env->get("HTTP_TRANSFER_ENCODING") == nullptr; | |
414 | } | |
415 | ||
416 | static inline bool is_v4_payload_streamed(const char* const exp_payload_hash) | |
417 | { | |
418 | return boost::equals(exp_payload_hash, AWS4_STREAMING_PAYLOAD_HASH); | |
419 | } | |
420 | ||
421 | std::string get_v4_canonical_qs(const req_info& info, bool using_qs); | |
422 | ||
423 | boost::optional<std::string> | |
424 | get_v4_canonical_headers(const req_info& info, | |
425 | const boost::string_view& signedheaders, | |
426 | bool using_qs, | |
427 | bool force_boto2_compat); | |
428 | ||
429 | extern sha256_digest_t | |
430 | get_v4_canon_req_hash(CephContext* cct, | |
431 | const boost::string_view& http_verb, | |
432 | const std::string& canonical_uri, | |
433 | const std::string& canonical_qs, | |
434 | const std::string& canonical_hdrs, | |
435 | const boost::string_view& signed_hdrs, | |
436 | const boost::string_view& request_payload_hash); | |
437 | ||
438 | AWSEngine::VersionAbstractor::string_to_sign_t | |
439 | get_v4_string_to_sign(CephContext* cct, | |
440 | const boost::string_view& algorithm, | |
441 | const boost::string_view& request_date, | |
442 | const boost::string_view& credential_scope, | |
443 | const sha256_digest_t& canonreq_hash); | |
444 | ||
445 | extern AWSEngine::VersionAbstractor::server_signature_t | |
446 | get_v4_signature(const boost::string_view& credential_scope, | |
447 | CephContext* const cct, | |
448 | const boost::string_view& secret_key, | |
449 | const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign); | |
450 | ||
451 | extern AWSEngine::VersionAbstractor::server_signature_t | |
452 | get_v2_signature(CephContext*, | |
453 | const std::string& secret_key, | |
454 | const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign); | |
455 | ||
456 | } /* namespace s3 */ | |
457 | } /* namespace auth */ | |
458 | } /* namespace rgw */ | |
7c673cae FG |
459 | |
460 | #endif |