]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_auth_s3.h
update sources to 12.2.7
[ceph.git] / ceph / src / rgw / rgw_auth_s3.h
CommitLineData
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
26namespace rgw {
27namespace auth {
28namespace s3 {
29
94b18763
FG
30static 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
33bool is_time_skew_ok(time_t t);
34
7c673cae
FG
35class 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
61public:
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
94template <class AbstractorT,
95 bool AllowAnonAccessT = false>
31f18b77
FG
96class 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
121public:
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
159class 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
233public:
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
276class 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
285public:
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
314void 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);
323bool rgw_create_s3_canonical_header(const req_info& info,
324 utime_t *header_time, /* out */
325 std::string& dest, /* out */
326 bool qsr);
327static inline std::tuple<bool, std::string, utime_t>
328rgw_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
336namespace rgw {
337namespace auth {
338namespace s3 {
339
340static constexpr char AWS4_HMAC_SHA256_STR[] = "AWS4-HMAC-SHA256";
224ce89b 341static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR[] = "AWS4-HMAC-SHA256-PAYLOAD";
31f18b77
FG
342
343static constexpr char AWS4_EMPTY_PAYLOAD_HASH[] = \
344 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
345
346static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD";
347
348static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \
349 "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
350
351int 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
359static 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
375static 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
400static 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
405static 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
416static 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
421std::string get_v4_canonical_qs(const req_info& info, bool using_qs);
422
423boost::optional<std::string>
424get_v4_canonical_headers(const req_info& info,
425 const boost::string_view& signedheaders,
426 bool using_qs,
427 bool force_boto2_compat);
428
429extern sha256_digest_t
430get_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
438AWSEngine::VersionAbstractor::string_to_sign_t
439get_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
445extern AWSEngine::VersionAbstractor::server_signature_t
446get_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
451extern AWSEngine::VersionAbstractor::server_signature_t
452get_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