]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_auth_s3.h
import 15.2.0 Octopus source
[ceph.git] / ceph / src / rgw / rgw_auth_s3.h
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
3
4 #ifndef CEPH_RGW_AUTH_S3_H
5 #define CEPH_RGW_AUTH_S3_H
6
7 #include <array>
8 #include <memory>
9 #include <string>
10 #include <tuple>
11
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"
18 #include "rgw_common.h"
19 #include "rgw_rest_s3.h"
20 #include "rgw_auth.h"
21 #include "rgw_auth_filters.h"
22 #include "rgw_auth_keystone.h"
23
24
25 namespace rgw {
26 namespace auth {
27 namespace s3 {
28
29 static constexpr auto RGW_AUTH_GRACE = std::chrono::minutes{15};
30
31 // returns true if the request time is within RGW_AUTH_GRACE of the current time
32 bool is_time_skew_ok(time_t t);
33
34 class STSAuthStrategy : public rgw::auth::Strategy,
35 public rgw::auth::RemoteApplier::Factory,
36 public rgw::auth::LocalApplier::Factory,
37 public rgw::auth::RoleApplier::Factory {
38 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t;
39 RGWCtl* const ctl;
40 rgw::auth::ImplicitTenants& implicit_tenant_context;
41
42 STSEngine sts_engine;
43
44 aplptr_t create_apl_remote(CephContext* const cct,
45 const req_state* const s,
46 rgw::auth::RemoteApplier::acl_strategy_t&& acl_alg,
47 const rgw::auth::RemoteApplier::AuthInfo &info
48 ) const override {
49 auto apl = rgw::auth::add_sysreq(cct, ctl, s,
50 rgw::auth::RemoteApplier(cct, ctl, std::move(acl_alg), info,
51 implicit_tenant_context,
52 rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3));
53 return aplptr_t(new decltype(apl)(std::move(apl)));
54 }
55
56 aplptr_t create_apl_local(CephContext* const cct,
57 const req_state* const s,
58 const RGWUserInfo& user_info,
59 const std::string& subuser,
60 const boost::optional<uint32_t>& perm_mask) const override {
61 auto apl = rgw::auth::add_sysreq(cct, ctl, s,
62 rgw::auth::LocalApplier(cct, user_info, subuser, perm_mask));
63 return aplptr_t(new decltype(apl)(std::move(apl)));
64 }
65
66 aplptr_t create_apl_role(CephContext* const cct,
67 const req_state* const s,
68 const string& role_name,
69 const rgw_user& user_id,
70 const vector<std::string>& role_policies) const override {
71 auto apl = rgw::auth::add_sysreq(cct, ctl, s,
72 rgw::auth::RoleApplier(cct, role_name, user_id, role_policies));
73 return aplptr_t(new decltype(apl)(std::move(apl)));
74 }
75
76 public:
77 STSAuthStrategy(CephContext* const cct,
78 RGWCtl* const ctl,
79 rgw::auth::ImplicitTenants& implicit_tenant_context,
80 AWSEngine::VersionAbstractor* const ver_abstractor)
81 : ctl(ctl),
82 implicit_tenant_context(implicit_tenant_context),
83 sts_engine(cct, ctl, *ver_abstractor,
84 static_cast<rgw::auth::LocalApplier::Factory*>(this),
85 static_cast<rgw::auth::RemoteApplier::Factory*>(this),
86 static_cast<rgw::auth::RoleApplier::Factory*>(this)) {
87 if (cct->_conf->rgw_s3_auth_use_sts) {
88 add_engine(Control::SUFFICIENT, sts_engine);
89 }
90 }
91
92 const char* get_name() const noexcept override {
93 return "rgw::auth::s3::STSAuthStrategy";
94 }
95 };
96
97 class ExternalAuthStrategy : public rgw::auth::Strategy,
98 public rgw::auth::RemoteApplier::Factory {
99 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t;
100 RGWCtl* const ctl;
101 rgw::auth::ImplicitTenants& implicit_tenant_context;
102
103 using keystone_config_t = rgw::keystone::CephCtxConfig;
104 using keystone_cache_t = rgw::keystone::TokenCache;
105 using secret_cache_t = rgw::auth::keystone::SecretCache;
106 using EC2Engine = rgw::auth::keystone::EC2Engine;
107
108 boost::optional <EC2Engine> keystone_engine;
109 LDAPEngine ldap_engine;
110
111 aplptr_t create_apl_remote(CephContext* const cct,
112 const req_state* const s,
113 rgw::auth::RemoteApplier::acl_strategy_t&& acl_alg,
114 const rgw::auth::RemoteApplier::AuthInfo &info
115 ) const override {
116 auto apl = rgw::auth::add_sysreq(cct, ctl, s,
117 rgw::auth::RemoteApplier(cct, ctl, std::move(acl_alg), info,
118 implicit_tenant_context,
119 rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3));
120 /* TODO(rzarzynski): replace with static_ptr. */
121 return aplptr_t(new decltype(apl)(std::move(apl)));
122 }
123
124 public:
125 ExternalAuthStrategy(CephContext* const cct,
126 RGWCtl* const ctl,
127 rgw::auth::ImplicitTenants& implicit_tenant_context,
128 AWSEngine::VersionAbstractor* const ver_abstractor)
129 : ctl(ctl),
130 implicit_tenant_context(implicit_tenant_context),
131 ldap_engine(cct, ctl, *ver_abstractor,
132 static_cast<rgw::auth::RemoteApplier::Factory*>(this)) {
133
134 if (cct->_conf->rgw_s3_auth_use_keystone &&
135 ! cct->_conf->rgw_keystone_url.empty()) {
136
137 keystone_engine.emplace(cct, ver_abstractor,
138 static_cast<rgw::auth::RemoteApplier::Factory*>(this),
139 keystone_config_t::get_instance(),
140 keystone_cache_t::get_instance<keystone_config_t>(),
141 secret_cache_t::get_instance());
142 add_engine(Control::SUFFICIENT, *keystone_engine);
143
144 }
145
146 if (ldap_engine.valid()) {
147 add_engine(Control::SUFFICIENT, ldap_engine);
148 }
149 }
150
151 const char* get_name() const noexcept override {
152 return "rgw::auth::s3::AWSv2ExternalAuthStrategy";
153 }
154 };
155
156
157 template <class AbstractorT,
158 bool AllowAnonAccessT = false>
159 class AWSAuthStrategy : public rgw::auth::Strategy,
160 public rgw::auth::LocalApplier::Factory {
161 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t;
162
163 static_assert(std::is_base_of<rgw::auth::s3::AWSEngine::VersionAbstractor,
164 AbstractorT>::value,
165 "AbstractorT must be a subclass of rgw::auth::s3::VersionAbstractor");
166
167 RGWCtl* const ctl;
168 AbstractorT ver_abstractor;
169
170 S3AnonymousEngine anonymous_engine;
171 ExternalAuthStrategy external_engines;
172 STSAuthStrategy sts_engine;
173 LocalEngine local_engine;
174
175 aplptr_t create_apl_local(CephContext* const cct,
176 const req_state* const s,
177 const RGWUserInfo& user_info,
178 const std::string& subuser,
179 const boost::optional<uint32_t>& perm_mask) const override {
180 auto apl = rgw::auth::add_sysreq(cct, ctl, s,
181 rgw::auth::LocalApplier(cct, user_info, subuser, perm_mask));
182 /* TODO(rzarzynski): replace with static_ptr. */
183 return aplptr_t(new decltype(apl)(std::move(apl)));
184 }
185
186 public:
187 using engine_map_t = std::map <std::string, std::reference_wrapper<const Engine>>;
188 void add_engines(const std::vector <std::string>& auth_order,
189 engine_map_t eng_map)
190 {
191 auto ctrl_flag = Control::SUFFICIENT;
192 for (const auto &eng : auth_order) {
193 // fallback to the last engine, in case of multiple engines, since ctrl
194 // flag is sufficient for others, error from earlier engine is returned
195 if (&eng == &auth_order.back() && eng_map.size() > 1) {
196 ctrl_flag = Control::FALLBACK;
197 }
198 if (const auto kv = eng_map.find(eng);
199 kv != eng_map.end()) {
200 add_engine(ctrl_flag, kv->second);
201 }
202 }
203 }
204
205 auto parse_auth_order(CephContext* const cct)
206 {
207 std::vector <std::string> result;
208
209 const std::set <std::string_view> allowed_auth = { "sts", "external", "local" };
210 std::vector <std::string> default_order = { "sts", "external", "local" };
211 // supplied strings may contain a space, so let's bypass that
212 boost::split(result, cct->_conf->rgw_s3_auth_order,
213 boost::is_any_of(", "), boost::token_compress_on);
214
215 if (std::any_of(result.begin(), result.end(),
216 [allowed_auth](std::string_view s)
217 { return allowed_auth.find(s) == allowed_auth.end();})){
218 return default_order;
219 }
220 return result;
221 }
222
223 AWSAuthStrategy(CephContext* const cct,
224 rgw::auth::ImplicitTenants& implicit_tenant_context,
225 RGWCtl* const ctl)
226 : ctl(ctl),
227 ver_abstractor(cct),
228 anonymous_engine(cct,
229 static_cast<rgw::auth::LocalApplier::Factory*>(this)),
230 external_engines(cct, ctl, implicit_tenant_context, &ver_abstractor),
231 sts_engine(cct, ctl, implicit_tenant_context, &ver_abstractor),
232 local_engine(cct, ctl, ver_abstractor,
233 static_cast<rgw::auth::LocalApplier::Factory*>(this)) {
234 /* The anonymous auth. */
235 if (AllowAnonAccessT) {
236 add_engine(Control::SUFFICIENT, anonymous_engine);
237 }
238
239 auto auth_order = parse_auth_order(cct);
240 engine_map_t engine_map;
241
242 /* STS Auth*/
243 if (! sts_engine.is_empty()) {
244 engine_map.insert(std::make_pair("sts", std::cref(sts_engine)));
245 }
246
247 /* The external auth. */
248 if (! external_engines.is_empty()) {
249 engine_map.insert(std::make_pair("external", std::cref(external_engines)));
250 }
251 /* The local auth. */
252 if (cct->_conf->rgw_s3_auth_use_rados) {
253 engine_map.insert(std::make_pair("local", std::cref(local_engine)));
254 }
255
256 add_engines(auth_order, engine_map);
257 }
258
259 const char* get_name() const noexcept override {
260 return "rgw::auth::s3::AWSAuthStrategy";
261 }
262 };
263
264
265 class AWSv4ComplMulti : public rgw::auth::Completer,
266 public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>,
267 public std::enable_shared_from_this<AWSv4ComplMulti> {
268 using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>;
269 using signing_key_t = sha256_digest_t;
270
271 CephContext* const cct;
272
273 const boost::string_view date;
274 const boost::string_view credential_scope;
275 const signing_key_t signing_key;
276
277 class ChunkMeta {
278 size_t data_offset_in_stream = 0;
279 size_t data_length = 0;
280 std::string signature;
281
282 ChunkMeta(const size_t data_starts_in_stream,
283 const size_t data_length,
284 const boost::string_ref signature)
285 : data_offset_in_stream(data_starts_in_stream),
286 data_length(data_length),
287 signature(signature.to_string()) {
288 }
289
290 explicit ChunkMeta(const boost::string_view& signature)
291 : signature(signature.to_string()) {
292 }
293
294 public:
295 static constexpr size_t SIG_SIZE = 64;
296
297 /* Let's suppose the data length fields can't exceed uint64_t. */
298 static constexpr size_t META_MAX_SIZE = \
299 sarrlen("\r\nffffffffffffffff;chunk-signature=") + SIG_SIZE + sarrlen("\r\n");
300
301 /* The metadata size of for the last, empty chunk. */
302 static constexpr size_t META_MIN_SIZE = \
303 sarrlen("0;chunk-signature=") + SIG_SIZE + sarrlen("\r\n");
304
305 /* Detect whether a given stream_pos fits in boundaries of a chunk. */
306 bool is_new_chunk_in_stream(size_t stream_pos) const;
307
308 /* Get the remaining data size. */
309 size_t get_data_size(size_t stream_pos) const;
310
311 const std::string& get_signature() const {
312 return signature;
313 }
314
315 /* Factory: create an object representing metadata of first, initial chunk
316 * in a stream. */
317 static ChunkMeta create_first(const boost::string_view& seed_signature) {
318 return ChunkMeta(seed_signature);
319 }
320
321 /* Factory: parse a block of META_MAX_SIZE bytes and creates an object
322 * representing non-first chunk in a stream. As the process is sequential
323 * and depends on the previous chunk, caller must pass it. */
324 static std::pair<ChunkMeta, size_t> create_next(CephContext* cct,
325 ChunkMeta&& prev,
326 const char* metabuf,
327 size_t metabuf_len);
328 } chunk_meta;
329
330 size_t stream_pos;
331 boost::container::static_vector<char, ChunkMeta::META_MAX_SIZE> parsing_buf;
332 ceph::crypto::SHA256* sha256_hash;
333 std::string prev_chunk_signature;
334
335 bool is_signature_mismatched();
336 std::string calc_chunk_signature(const std::string& payload_hash) const;
337
338 public:
339 /* We need the constructor to be public because of the std::make_shared that
340 * is employed by the create() method. */
341 AWSv4ComplMulti(const req_state* const s,
342 boost::string_view date,
343 boost::string_view credential_scope,
344 boost::string_view seed_signature,
345 const signing_key_t& signing_key)
346 : io_base_t(nullptr),
347 cct(s->cct),
348 date(std::move(date)),
349 credential_scope(std::move(credential_scope)),
350 signing_key(signing_key),
351
352 /* The evolving state. */
353 chunk_meta(ChunkMeta::create_first(seed_signature)),
354 stream_pos(0),
355 sha256_hash(calc_hash_sha256_open_stream()),
356 prev_chunk_signature(std::move(seed_signature)) {
357 }
358
359 ~AWSv4ComplMulti() {
360 if (sha256_hash) {
361 calc_hash_sha256_close_stream(&sha256_hash);
362 }
363 }
364
365 /* rgw::io::DecoratedRestfulClient. */
366 size_t recv_body(char* buf, size_t max) override;
367
368 /* rgw::auth::Completer. */
369 void modify_request_state(const DoutPrefixProvider* dpp, req_state* s_rw) override;
370 bool complete() override;
371
372 /* Factories. */
373 static cmplptr_t create(const req_state* s,
374 boost::string_view date,
375 boost::string_view credential_scope,
376 boost::string_view seed_signature,
377 const boost::optional<std::string>& secret_key);
378
379 };
380
381 class AWSv4ComplSingle : public rgw::auth::Completer,
382 public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>,
383 public std::enable_shared_from_this<AWSv4ComplSingle> {
384 using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>;
385
386 CephContext* const cct;
387 const char* const expected_request_payload_hash;
388 ceph::crypto::SHA256* sha256_hash = nullptr;
389
390 public:
391 /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). We need
392 * the constructor to be public because of the std::make_shared employed by
393 * the create() method. */
394 explicit AWSv4ComplSingle(const req_state* const s);
395
396 ~AWSv4ComplSingle() {
397 if (sha256_hash) {
398 calc_hash_sha256_close_stream(&sha256_hash);
399 }
400 }
401
402 /* rgw::io::DecoratedRestfulClient. */
403 size_t recv_body(char* buf, size_t max) override;
404
405 /* rgw::auth::Completer. */
406 void modify_request_state(const DoutPrefixProvider* dpp, req_state* s_rw) override;
407 bool complete() override;
408
409 /* Factories. */
410 static cmplptr_t create(const req_state* s,
411 const boost::optional<std::string>&);
412
413 };
414
415 } /* namespace s3 */
416 } /* namespace auth */
417 } /* namespace rgw */
418
419 void rgw_create_s3_canonical_header(
420 const char *method,
421 const char *content_md5,
422 const char *content_type,
423 const char *date,
424 const meta_map_t& meta_map,
425 const meta_map_t& qs_map,
426 const char *request_uri,
427 const std::map<std::string, std::string>& sub_resources,
428 std::string& dest_str);
429 bool rgw_create_s3_canonical_header(const req_info& info,
430 utime_t *header_time, /* out */
431 std::string& dest, /* out */
432 bool qsr);
433 static inline std::tuple<bool, std::string, utime_t>
434 rgw_create_s3_canonical_header(const req_info& info, const bool qsr) {
435 std::string dest;
436 utime_t header_time;
437
438 const bool ok = rgw_create_s3_canonical_header(info, &header_time, dest, qsr);
439 return std::make_tuple(ok, dest, header_time);
440 }
441
442 namespace rgw {
443 namespace auth {
444 namespace s3 {
445
446 static constexpr char AWS4_HMAC_SHA256_STR[] = "AWS4-HMAC-SHA256";
447 static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR[] = "AWS4-HMAC-SHA256-PAYLOAD";
448
449 static constexpr char AWS4_EMPTY_PAYLOAD_HASH[] = \
450 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
451
452 static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD";
453
454 static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \
455 "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
456
457 int parse_v4_credentials(const req_info& info, /* in */
458 boost::string_view& access_key_id, /* out */
459 boost::string_view& credential_scope, /* out */
460 boost::string_view& signedheaders, /* out */
461 boost::string_view& signature, /* out */
462 boost::string_view& date, /* out */
463 boost::string_view& session_token, /* out */
464 const bool using_qs); /* in */
465
466 static inline bool char_needs_aws4_escaping(const char c, bool encode_slash)
467 {
468 if ((c >= 'a' && c <= 'z') ||
469 (c >= 'A' && c <= 'Z') ||
470 (c >= '0' && c <= '9')) {
471 return false;
472 }
473
474 switch (c) {
475 case '-':
476 case '_':
477 case '.':
478 case '~':
479 return false;
480 }
481
482 if (c == '/' && !encode_slash)
483 return false;
484
485 return true;
486 }
487
488 static inline std::string aws4_uri_encode(const std::string& src, bool encode_slash)
489 {
490 std::string result;
491
492 for (const std::string::value_type c : src) {
493 if (char_needs_aws4_escaping(c, encode_slash)) {
494 rgw_uri_escape_char(c, result);
495 } else {
496 result.push_back(c);
497 }
498 }
499
500 return result;
501 }
502
503 static inline std::string aws4_uri_recode(const boost::string_view& src, bool encode_slash)
504 {
505 std::string decoded = url_decode(src);
506 return aws4_uri_encode(decoded, encode_slash);
507 }
508
509 static inline std::string get_v4_canonical_uri(const req_info& info) {
510 /* The code should normalize according to RFC 3986 but S3 does NOT do path
511 * normalization that SigV4 typically does. This code follows the same
512 * approach that boto library. See auth.py:canonical_uri(...). */
513
514 std::string canonical_uri = aws4_uri_recode(info.request_uri_aws4, false);
515
516 if (canonical_uri.empty()) {
517 canonical_uri = "/";
518 } else {
519 boost::replace_all(canonical_uri, "+", "%20");
520 }
521
522 return canonical_uri;
523 }
524
525 static inline const string calc_v4_payload_hash(const string& payload)
526 {
527 ceph::crypto::SHA256* sha256_hash = calc_hash_sha256_open_stream();
528 calc_hash_sha256_update_stream(sha256_hash, payload.c_str(), payload.length());
529 const auto payload_hash = calc_hash_sha256_close_stream(&sha256_hash);
530 return payload_hash;
531 }
532
533 static inline const char* get_v4_exp_payload_hash(const req_info& info)
534 {
535 /* In AWSv4 the hash of real, transferred payload IS NOT necessary to form
536 * a Canonical Request, and thus verify a Signature. x-amz-content-sha256
537 * header lets get the information very early -- before seeing first byte
538 * of HTTP body. As a consequence, we can decouple Signature verification
539 * from payload's fingerprint check. */
540 const char *expected_request_payload_hash = \
541 info.env->get("HTTP_X_AMZ_CONTENT_SHA256");
542
543 if (!expected_request_payload_hash) {
544 /* An HTTP client MUST send x-amz-content-sha256. The single exception
545 * is the case of using the Query Parameters where "UNSIGNED-PAYLOAD"
546 * literals are used for crafting Canonical Request:
547 *
548 * You don't include a payload hash in the Canonical Request, because
549 * when you create a presigned URL, you don't know the payload content
550 * because the URL is used to upload an arbitrary payload. Instead, you
551 * use a constant string UNSIGNED-PAYLOAD. */
552 expected_request_payload_hash = AWS4_UNSIGNED_PAYLOAD_HASH;
553 }
554
555 return expected_request_payload_hash;
556 }
557
558 static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash)
559 {
560 return boost::equals(exp_payload_hash, AWS4_UNSIGNED_PAYLOAD_HASH);
561 }
562
563 static inline bool is_v4_payload_empty(const req_state* const s)
564 {
565 /* from rfc2616 - 4.3 Message Body
566 *
567 * "The presence of a message-body in a request is signaled by the inclusion
568 * of a Content-Length or Transfer-Encoding header field in the request's
569 * message-headers." */
570 return s->content_length == 0 &&
571 s->info.env->get("HTTP_TRANSFER_ENCODING") == nullptr;
572 }
573
574 static inline bool is_v4_payload_streamed(const char* const exp_payload_hash)
575 {
576 return boost::equals(exp_payload_hash, AWS4_STREAMING_PAYLOAD_HASH);
577 }
578
579 std::string get_v4_canonical_qs(const req_info& info, bool using_qs);
580
581 boost::optional<std::string>
582 get_v4_canonical_headers(const req_info& info,
583 const boost::string_view& signedheaders,
584 bool using_qs,
585 bool force_boto2_compat);
586
587 extern sha256_digest_t
588 get_v4_canon_req_hash(CephContext* cct,
589 const boost::string_view& http_verb,
590 const std::string& canonical_uri,
591 const std::string& canonical_qs,
592 const std::string& canonical_hdrs,
593 const boost::string_view& signed_hdrs,
594 const boost::string_view& request_payload_hash);
595
596 AWSEngine::VersionAbstractor::string_to_sign_t
597 get_v4_string_to_sign(CephContext* cct,
598 const boost::string_view& algorithm,
599 const boost::string_view& request_date,
600 const boost::string_view& credential_scope,
601 const sha256_digest_t& canonreq_hash);
602
603 extern AWSEngine::VersionAbstractor::server_signature_t
604 get_v4_signature(const boost::string_view& credential_scope,
605 CephContext* const cct,
606 const boost::string_view& secret_key,
607 const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign);
608
609 extern AWSEngine::VersionAbstractor::server_signature_t
610 get_v2_signature(CephContext*,
611 const std::string& secret_key,
612 const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign);
613 } /* namespace s3 */
614 } /* namespace auth */
615 } /* namespace rgw */
616
617 #endif