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