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