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