]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_auth_s3.h
update sources to 12.2.8
[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
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
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
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
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;
39 rgw::auth::ImplicitTenants& implicit_tenant_context;
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
45 boost::optional <EC2Engine> keystone_engine;
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,
55 implicit_tenant_context,
56 rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3));
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,
64 rgw::auth::ImplicitTenants& implicit_tenant_context,
65 AWSEngine::VersionAbstractor* const ver_abstractor)
66 : store(store),
67 implicit_tenant_context(implicit_tenant_context),
68 ldap_engine(cct, store, *ver_abstractor,
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()) {
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
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
94 template <class AbstractorT,
95 bool AllowAnonAccessT = false>
96 class AWSAuthStrategy : public rgw::auth::Strategy,
97 public rgw::auth::LocalApplier::Factory {
98 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t;
99
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");
103
104 RGWRados* const store;
105 AbstractorT ver_abstractor;
106
107 S3AnonymousEngine anonymous_engine;
108 ExternalAuthStrategy external_engines;
109 LocalEngine local_engine;
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:
122 using engine_map_t = std::map <std::string, std::reference_wrapper<const Engine>>;
123 void add_engines(const std::vector <std::string>& auth_order,
124 engine_map_t eng_map)
125 {
126 auto ctrl_flag = Control::SUFFICIENT;
127 for (const auto &eng : auth_order) {
128 // fallback to the last engine, in case of multiple engines, since ctrl
129 // flag is sufficient for others, error from earlier engine is returned
130 if (&eng == &auth_order.back() && eng_map.size() > 1) {
131 ctrl_flag = Control::FALLBACK;
132 }
133 const auto kv = eng_map.find(eng);
134 if (kv != eng_map.end()) {
135 add_engine(ctrl_flag, kv->second);
136 }
137 }
138 }
139
140 std::vector<std::string> parse_auth_order(CephContext* const cct)
141 {
142 std::vector <std::string> result;
143
144 const std::set <boost::string_view> allowed_auth = { "external", "local" };
145 std::vector <std::string> default_order = { "external", "local"};
146 // supplied strings may contain a space, so let's bypass that
147 boost::split(result, cct->_conf->rgw_s3_auth_order,
148 boost::is_any_of(", "), boost::token_compress_on);
149
150 if (std::any_of(result.begin(), result.end(),
151 [allowed_auth](boost::string_view s)
152 { return allowed_auth.find(s) == allowed_auth.end();})){
153 return default_order;
154 }
155 return result;
156 }
157
158 AWSAuthStrategy(CephContext* const cct,
159 rgw::auth::ImplicitTenants& implicit_tenant_context,
160 RGWRados* const store)
161 : store(store),
162 ver_abstractor(cct),
163 anonymous_engine(cct,
164 static_cast<rgw::auth::LocalApplier::Factory*>(this)),
165 external_engines(cct, store, implicit_tenant_context, &ver_abstractor),
166 local_engine(cct, store, ver_abstractor,
167 static_cast<rgw::auth::LocalApplier::Factory*>(this)) {
168 /* The anynoymous auth. */
169 if (AllowAnonAccessT) {
170 add_engine(Control::SUFFICIENT, anonymous_engine);
171 }
172
173 auto auth_order = parse_auth_order(cct);
174 engine_map_t engine_map;
175 /* The external auth. */
176 if (! external_engines.is_empty()) {
177 engine_map.insert(std::make_pair("external", std::cref(external_engines)));
178 }
179 /* The local auth. */
180 if (cct->_conf->rgw_s3_auth_use_rados) {
181 engine_map.insert(std::make_pair("local", std::cref(local_engine)));
182 }
183 add_engines(auth_order, engine_map);
184 }
185
186 const char* get_name() const noexcept override {
187 return "rgw::auth::s3::AWSAuthStrategy";
188 }
189 };
190
191
192 class AWSv4ComplMulti : public rgw::auth::Completer,
193 public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>,
194 public std::enable_shared_from_this<AWSv4ComplMulti> {
195 using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>;
196 using signing_key_t = std::array<unsigned char,
197 CEPH_CRYPTO_HMACSHA256_DIGESTSIZE>;
198
199 CephContext* const cct;
200
201 const boost::string_view date;
202 const boost::string_view credential_scope;
203 const signing_key_t signing_key;
204
205 class ChunkMeta {
206 size_t data_offset_in_stream = 0;
207 size_t data_length = 0;
208 std::string signature;
209
210 ChunkMeta(const size_t data_starts_in_stream,
211 const size_t data_length,
212 const boost::string_ref signature)
213 : data_offset_in_stream(data_starts_in_stream),
214 data_length(data_length),
215 signature(signature.to_string()) {
216 }
217
218 ChunkMeta(const boost::string_view& signature)
219 : signature(signature.to_string()) {
220 }
221
222 public:
223 static constexpr size_t SIG_SIZE = 64;
224
225 /* Let's suppose the data length fields can't exceed uint64_t. */
226 static constexpr size_t META_MAX_SIZE = \
227 sarrlen("\r\nffffffffffffffff;chunk-signature=") + SIG_SIZE + sarrlen("\r\n");
228
229 /* The metadata size of for the last, empty chunk. */
230 static constexpr size_t META_MIN_SIZE = \
231 sarrlen("0;chunk-signature=") + SIG_SIZE + sarrlen("\r\n");
232
233 /* Detect whether a given stream_pos fits in boundaries of a chunk. */
234 bool is_new_chunk_in_stream(size_t stream_pos) const;
235
236 /* Get the remaining data size. */
237 size_t get_data_size(size_t stream_pos) const;
238
239 const std::string& get_signature() const {
240 return signature;
241 }
242
243 /* Factory: create an object representing metadata of first, initial chunk
244 * in a stream. */
245 static ChunkMeta create_first(const boost::string_view& seed_signature) {
246 return ChunkMeta(seed_signature);
247 }
248
249 /* Factory: parse a block of META_MAX_SIZE bytes and creates an object
250 * representing non-first chunk in a stream. As the process is sequential
251 * and depends on the previous chunk, caller must pass it. */
252 static std::pair<ChunkMeta, size_t> create_next(CephContext* cct,
253 ChunkMeta&& prev,
254 const char* metabuf,
255 size_t metabuf_len);
256 } chunk_meta;
257
258 size_t stream_pos;
259 boost::container::static_vector<char, ChunkMeta::META_MAX_SIZE> parsing_buf;
260 ceph::crypto::SHA256* sha256_hash;
261 std::string prev_chunk_signature;
262
263 bool is_signature_mismatched();
264 std::string calc_chunk_signature(const std::string& payload_hash) const;
265
266 public:
267 /* We need the constructor to be public because of the std::make_shared that
268 * is employed by the create() method. */
269 AWSv4ComplMulti(const req_state* const s,
270 boost::string_view date,
271 boost::string_view credential_scope,
272 boost::string_view seed_signature,
273 const signing_key_t& signing_key)
274 : io_base_t(nullptr),
275 cct(s->cct),
276 date(std::move(date)),
277 credential_scope(std::move(credential_scope)),
278 signing_key(signing_key),
279
280 /* The evolving state. */
281 chunk_meta(ChunkMeta::create_first(seed_signature)),
282 stream_pos(0),
283 sha256_hash(calc_hash_sha256_open_stream()),
284 prev_chunk_signature(std::move(seed_signature)) {
285 }
286
287 ~AWSv4ComplMulti() {
288 if (sha256_hash) {
289 calc_hash_sha256_close_stream(&sha256_hash);
290 }
291 }
292
293 /* rgw::io::DecoratedRestfulClient. */
294 size_t recv_body(char* buf, size_t max) override;
295
296 /* rgw::auth::Completer. */
297 void modify_request_state(req_state* s_rw) override;
298 bool complete() override;
299
300 /* Factories. */
301 static cmplptr_t create(const req_state* s,
302 boost::string_view date,
303 boost::string_view credential_scope,
304 boost::string_view seed_signature,
305 const boost::optional<std::string>& secret_key);
306
307 };
308
309 class AWSv4ComplSingle : public rgw::auth::Completer,
310 public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>,
311 public std::enable_shared_from_this<AWSv4ComplSingle> {
312 using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>;
313
314 CephContext* const cct;
315 const char* const expected_request_payload_hash;
316 ceph::crypto::SHA256* sha256_hash = nullptr;
317
318 public:
319 /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). We need
320 * the constructor to be public because of the std::make_shared employed by
321 * the create() method. */
322 AWSv4ComplSingle(const req_state* const s);
323
324 ~AWSv4ComplSingle() {
325 if (sha256_hash) {
326 calc_hash_sha256_close_stream(&sha256_hash);
327 }
328 }
329
330 /* rgw::io::DecoratedRestfulClient. */
331 size_t recv_body(char* buf, size_t max) override;
332
333 /* rgw::auth::Completer. */
334 void modify_request_state(req_state* s_rw) override;
335 bool complete() override;
336
337 /* Factories. */
338 static cmplptr_t create(const req_state* s,
339 const boost::optional<std::string>&);
340
341 };
342
343 } /* namespace s3 */
344 } /* namespace auth */
345 } /* namespace rgw */
346
347 void rgw_create_s3_canonical_header(
348 const char *method,
349 const char *content_md5,
350 const char *content_type,
351 const char *date,
352 const std::map<std::string, std::string>& meta_map,
353 const char *request_uri,
354 const std::map<std::string, std::string>& sub_resources,
355 std::string& dest_str);
356 bool rgw_create_s3_canonical_header(const req_info& info,
357 utime_t *header_time, /* out */
358 std::string& dest, /* out */
359 bool qsr);
360 static inline std::tuple<bool, std::string, utime_t>
361 rgw_create_s3_canonical_header(const req_info& info, const bool qsr) {
362 std::string dest;
363 utime_t header_time;
364
365 const bool ok = rgw_create_s3_canonical_header(info, &header_time, dest, qsr);
366 return std::make_tuple(ok, dest, header_time);
367 }
368
369 namespace rgw {
370 namespace auth {
371 namespace s3 {
372
373 static constexpr char AWS4_HMAC_SHA256_STR[] = "AWS4-HMAC-SHA256";
374 static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR[] = "AWS4-HMAC-SHA256-PAYLOAD";
375
376 static constexpr char AWS4_EMPTY_PAYLOAD_HASH[] = \
377 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
378
379 static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD";
380
381 static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \
382 "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
383
384 int parse_credentials(const req_info& info, /* in */
385 boost::string_view& access_key_id, /* out */
386 boost::string_view& credential_scope, /* out */
387 boost::string_view& signedheaders, /* out */
388 boost::string_view& signature, /* out */
389 boost::string_view& date, /* out */
390 bool& using_qs); /* out */
391
392 static inline std::string get_v4_canonical_uri(const req_info& info) {
393 /* The code should normalize according to RFC 3986 but S3 does NOT do path
394 * normalization that SigV4 typically does. This code follows the same
395 * approach that boto library. See auth.py:canonical_uri(...). */
396
397 std::string canonical_uri = info.request_uri_aws4;
398
399 if (canonical_uri.empty()) {
400 canonical_uri = "/";
401 } else {
402 boost::replace_all(canonical_uri, "+", "%20");
403 }
404
405 return canonical_uri;
406 }
407
408 static inline const char* get_v4_exp_payload_hash(const req_info& info)
409 {
410 /* In AWSv4 the hash of real, transfered payload IS NOT necessary to form
411 * a Canonical Request, and thus verify a Signature. x-amz-content-sha256
412 * header lets get the information very early -- before seeing first byte
413 * of HTTP body. As a consequence, we can decouple Signature verification
414 * from payload's fingerprint check. */
415 const char *expected_request_payload_hash = \
416 info.env->get("HTTP_X_AMZ_CONTENT_SHA256");
417
418 if (!expected_request_payload_hash) {
419 /* An HTTP client MUST send x-amz-content-sha256. The single exception
420 * is the case of using the Query Parameters where "UNSIGNED-PAYLOAD"
421 * literals are used for crafting Canonical Request:
422 *
423 * You don't include a payload hash in the Canonical Request, because
424 * when you create a presigned URL, you don't know the payload content
425 * because the URL is used to upload an arbitrary payload. Instead, you
426 * use a constant string UNSIGNED-PAYLOAD. */
427 expected_request_payload_hash = AWS4_UNSIGNED_PAYLOAD_HASH;
428 }
429
430 return expected_request_payload_hash;
431 }
432
433 static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash)
434 {
435 return boost::equals(exp_payload_hash, AWS4_UNSIGNED_PAYLOAD_HASH);
436 }
437
438 static inline bool is_v4_payload_empty(const req_state* const s)
439 {
440 /* from rfc2616 - 4.3 Message Body
441 *
442 * "The presence of a message-body in a request is signaled by the inclusion
443 * of a Content-Length or Transfer-Encoding header field in the request's
444 * message-headers." */
445 return s->content_length == 0 &&
446 s->info.env->get("HTTP_TRANSFER_ENCODING") == nullptr;
447 }
448
449 static inline bool is_v4_payload_streamed(const char* const exp_payload_hash)
450 {
451 return boost::equals(exp_payload_hash, AWS4_STREAMING_PAYLOAD_HASH);
452 }
453
454 std::string get_v4_canonical_qs(const req_info& info, bool using_qs);
455
456 boost::optional<std::string>
457 get_v4_canonical_headers(const req_info& info,
458 const boost::string_view& signedheaders,
459 bool using_qs,
460 bool force_boto2_compat);
461
462 extern sha256_digest_t
463 get_v4_canon_req_hash(CephContext* cct,
464 const boost::string_view& http_verb,
465 const std::string& canonical_uri,
466 const std::string& canonical_qs,
467 const std::string& canonical_hdrs,
468 const boost::string_view& signed_hdrs,
469 const boost::string_view& request_payload_hash);
470
471 AWSEngine::VersionAbstractor::string_to_sign_t
472 get_v4_string_to_sign(CephContext* cct,
473 const boost::string_view& algorithm,
474 const boost::string_view& request_date,
475 const boost::string_view& credential_scope,
476 const sha256_digest_t& canonreq_hash);
477
478 extern AWSEngine::VersionAbstractor::server_signature_t
479 get_v4_signature(const boost::string_view& credential_scope,
480 CephContext* const cct,
481 const boost::string_view& secret_key,
482 const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign);
483
484 extern AWSEngine::VersionAbstractor::server_signature_t
485 get_v2_signature(CephContext*,
486 const std::string& secret_key,
487 const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign);
488
489 } /* namespace s3 */
490 } /* namespace auth */
491 } /* namespace rgw */
492
493 #endif