]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
9f95a23c | 2 | // vim: ts=8 sw=2 smarttab ft=cpp |
7c673cae | 3 | |
1e59de90 | 4 | #pragma once |
7c673cae | 5 | |
31f18b77 FG |
6 | #include <array> |
7 | #include <memory> | |
7c673cae | 8 | #include <string> |
f67539c2 | 9 | #include <string_view> |
7c673cae FG |
10 | #include <tuple> |
11 | ||
31f18b77 FG |
12 | #include <boost/algorithm/string.hpp> |
13 | #include <boost/container/static_vector.hpp> | |
31f18b77 FG |
14 | |
15 | #include "common/sstring.hh" | |
7c673cae FG |
16 | #include "rgw_common.h" |
17 | #include "rgw_rest_s3.h" | |
7c673cae FG |
18 | #include "rgw_auth.h" |
19 | #include "rgw_auth_filters.h" | |
20 | #include "rgw_auth_keystone.h" | |
21 | ||
22 | ||
23 | namespace rgw { | |
24 | namespace auth { | |
25 | namespace s3 { | |
26 | ||
94b18763 FG |
27 | static constexpr auto RGW_AUTH_GRACE = std::chrono::minutes{15}; |
28 | ||
29 | // returns true if the request time is within RGW_AUTH_GRACE of the current time | |
30 | bool is_time_skew_ok(time_t t); | |
31 | ||
11fdf7f2 TL |
32 | class STSAuthStrategy : public rgw::auth::Strategy, |
33 | public rgw::auth::RemoteApplier::Factory, | |
34 | public rgw::auth::LocalApplier::Factory, | |
35 | public rgw::auth::RoleApplier::Factory { | |
36 | typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; | |
1e59de90 TL |
37 | rgw::sal::Driver* driver; |
38 | const rgw::auth::ImplicitTenants& implicit_tenant_context; | |
11fdf7f2 TL |
39 | |
40 | STSEngine sts_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, | |
2a845540 | 45 | const rgw::auth::RemoteApplier::AuthInfo &info) const override { |
1e59de90 TL |
46 | auto apl = rgw::auth::add_sysreq(cct, driver, s, |
47 | rgw::auth::RemoteApplier(cct, driver, std::move(acl_alg), info, | |
9f95a23c TL |
48 | implicit_tenant_context, |
49 | rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3)); | |
11fdf7f2 TL |
50 | return aplptr_t(new decltype(apl)(std::move(apl))); |
51 | } | |
52 | ||
53 | aplptr_t create_apl_local(CephContext* const cct, | |
54 | const req_state* const s, | |
55 | const RGWUserInfo& user_info, | |
56 | const std::string& subuser, | |
2a845540 TL |
57 | const std::optional<uint32_t>& perm_mask, |
58 | const std::string& access_key_id) const override { | |
1e59de90 | 59 | auto apl = rgw::auth::add_sysreq(cct, driver, s, |
2a845540 | 60 | rgw::auth::LocalApplier(cct, user_info, subuser, perm_mask, access_key_id)); |
11fdf7f2 TL |
61 | return aplptr_t(new decltype(apl)(std::move(apl))); |
62 | } | |
63 | ||
64 | aplptr_t create_apl_role(CephContext* const cct, | |
65 | const req_state* const s, | |
f91f0fd5 | 66 | const rgw::auth::RoleApplier::Role& role, |
20effc67 | 67 | const rgw::auth::RoleApplier::TokenAttrs& token_attrs) const override { |
1e59de90 | 68 | auto apl = rgw::auth::add_sysreq(cct, driver, s, |
20effc67 | 69 | rgw::auth::RoleApplier(cct, role, token_attrs)); |
11fdf7f2 TL |
70 | return aplptr_t(new decltype(apl)(std::move(apl))); |
71 | } | |
72 | ||
73 | public: | |
74 | STSAuthStrategy(CephContext* const cct, | |
1e59de90 TL |
75 | rgw::sal::Driver* driver, |
76 | const rgw::auth::ImplicitTenants& implicit_tenant_context, | |
11fdf7f2 | 77 | AWSEngine::VersionAbstractor* const ver_abstractor) |
1e59de90 | 78 | : driver(driver), |
9f95a23c | 79 | implicit_tenant_context(implicit_tenant_context), |
1e59de90 | 80 | sts_engine(cct, driver, *ver_abstractor, |
11fdf7f2 TL |
81 | static_cast<rgw::auth::LocalApplier::Factory*>(this), |
82 | static_cast<rgw::auth::RemoteApplier::Factory*>(this), | |
83 | static_cast<rgw::auth::RoleApplier::Factory*>(this)) { | |
84 | if (cct->_conf->rgw_s3_auth_use_sts) { | |
85 | add_engine(Control::SUFFICIENT, sts_engine); | |
86 | } | |
87 | } | |
88 | ||
89 | const char* get_name() const noexcept override { | |
90 | return "rgw::auth::s3::STSAuthStrategy"; | |
91 | } | |
92 | }; | |
93 | ||
7c673cae FG |
94 | class ExternalAuthStrategy : public rgw::auth::Strategy, |
95 | public rgw::auth::RemoteApplier::Factory { | |
96 | typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; | |
1e59de90 TL |
97 | rgw::sal::Driver* driver; |
98 | const rgw::auth::ImplicitTenants& implicit_tenant_context; | |
7c673cae FG |
99 | |
100 | using keystone_config_t = rgw::keystone::CephCtxConfig; | |
101 | using keystone_cache_t = rgw::keystone::TokenCache; | |
9f95a23c | 102 | using secret_cache_t = rgw::auth::keystone::SecretCache; |
7c673cae FG |
103 | using EC2Engine = rgw::auth::keystone::EC2Engine; |
104 | ||
3efd9988 | 105 | boost::optional <EC2Engine> keystone_engine; |
7c673cae FG |
106 | LDAPEngine ldap_engine; |
107 | ||
108 | aplptr_t create_apl_remote(CephContext* const cct, | |
109 | const req_state* const s, | |
110 | rgw::auth::RemoteApplier::acl_strategy_t&& acl_alg, | |
2a845540 | 111 | const rgw::auth::RemoteApplier::AuthInfo &info) const override { |
1e59de90 TL |
112 | auto apl = rgw::auth::add_sysreq(cct, driver, s, |
113 | rgw::auth::RemoteApplier(cct, driver, std::move(acl_alg), info, | |
9f95a23c TL |
114 | implicit_tenant_context, |
115 | rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3)); | |
7c673cae FG |
116 | /* TODO(rzarzynski): replace with static_ptr. */ |
117 | return aplptr_t(new decltype(apl)(std::move(apl))); | |
118 | } | |
119 | ||
120 | public: | |
121 | ExternalAuthStrategy(CephContext* const cct, | |
1e59de90 TL |
122 | rgw::sal::Driver* driver, |
123 | const rgw::auth::ImplicitTenants& implicit_tenant_context, | |
31f18b77 | 124 | AWSEngine::VersionAbstractor* const ver_abstractor) |
1e59de90 | 125 | : driver(driver), |
9f95a23c | 126 | implicit_tenant_context(implicit_tenant_context), |
1e59de90 | 127 | ldap_engine(cct, driver, *ver_abstractor, |
7c673cae FG |
128 | static_cast<rgw::auth::RemoteApplier::Factory*>(this)) { |
129 | ||
130 | if (cct->_conf->rgw_s3_auth_use_keystone && | |
131 | ! cct->_conf->rgw_keystone_url.empty()) { | |
3efd9988 FG |
132 | |
133 | keystone_engine.emplace(cct, ver_abstractor, | |
134 | static_cast<rgw::auth::RemoteApplier::Factory*>(this), | |
135 | keystone_config_t::get_instance(), | |
9f95a23c TL |
136 | keystone_cache_t::get_instance<keystone_config_t>(), |
137 | secret_cache_t::get_instance()); | |
3efd9988 FG |
138 | add_engine(Control::SUFFICIENT, *keystone_engine); |
139 | ||
7c673cae FG |
140 | } |
141 | ||
a8e16298 | 142 | if (ldap_engine.valid()) { |
7c673cae FG |
143 | add_engine(Control::SUFFICIENT, ldap_engine); |
144 | } | |
145 | } | |
146 | ||
147 | const char* get_name() const noexcept override { | |
148 | return "rgw::auth::s3::AWSv2ExternalAuthStrategy"; | |
149 | } | |
150 | }; | |
151 | ||
152 | ||
d2e6a577 FG |
153 | template <class AbstractorT, |
154 | bool AllowAnonAccessT = false> | |
31f18b77 FG |
155 | class AWSAuthStrategy : public rgw::auth::Strategy, |
156 | public rgw::auth::LocalApplier::Factory { | |
7c673cae FG |
157 | typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t; |
158 | ||
31f18b77 FG |
159 | static_assert(std::is_base_of<rgw::auth::s3::AWSEngine::VersionAbstractor, |
160 | AbstractorT>::value, | |
161 | "AbstractorT must be a subclass of rgw::auth::s3::VersionAbstractor"); | |
7c673cae | 162 | |
1e59de90 | 163 | rgw::sal::Driver* driver; |
31f18b77 | 164 | AbstractorT ver_abstractor; |
7c673cae | 165 | |
d2e6a577 | 166 | S3AnonymousEngine anonymous_engine; |
7c673cae | 167 | ExternalAuthStrategy external_engines; |
11fdf7f2 | 168 | STSAuthStrategy sts_engine; |
31f18b77 | 169 | LocalEngine local_engine; |
7c673cae FG |
170 | |
171 | aplptr_t create_apl_local(CephContext* const cct, | |
172 | const req_state* const s, | |
173 | const RGWUserInfo& user_info, | |
11fdf7f2 | 174 | const std::string& subuser, |
2a845540 TL |
175 | const std::optional<uint32_t>& perm_mask, |
176 | const std::string& access_key_id) const override { | |
1e59de90 | 177 | auto apl = rgw::auth::add_sysreq(cct, driver, s, |
2a845540 | 178 | rgw::auth::LocalApplier(cct, user_info, subuser, perm_mask, access_key_id)); |
7c673cae FG |
179 | /* TODO(rzarzynski): replace with static_ptr. */ |
180 | return aplptr_t(new decltype(apl)(std::move(apl))); | |
181 | } | |
182 | ||
183 | public: | |
1adf2230 AA |
184 | using engine_map_t = std::map <std::string, std::reference_wrapper<const Engine>>; |
185 | void add_engines(const std::vector <std::string>& auth_order, | |
186 | engine_map_t eng_map) | |
187 | { | |
188 | auto ctrl_flag = Control::SUFFICIENT; | |
189 | for (const auto &eng : auth_order) { | |
190 | // fallback to the last engine, in case of multiple engines, since ctrl | |
191 | // flag is sufficient for others, error from earlier engine is returned | |
192 | if (&eng == &auth_order.back() && eng_map.size() > 1) { | |
193 | ctrl_flag = Control::FALLBACK; | |
194 | } | |
11fdf7f2 TL |
195 | if (const auto kv = eng_map.find(eng); |
196 | kv != eng_map.end()) { | |
1adf2230 AA |
197 | add_engine(ctrl_flag, kv->second); |
198 | } | |
199 | } | |
200 | } | |
201 | ||
11fdf7f2 | 202 | auto parse_auth_order(CephContext* const cct) |
1adf2230 AA |
203 | { |
204 | std::vector <std::string> result; | |
205 | ||
11fdf7f2 TL |
206 | const std::set <std::string_view> allowed_auth = { "sts", "external", "local" }; |
207 | std::vector <std::string> default_order = { "sts", "external", "local" }; | |
1adf2230 AA |
208 | // supplied strings may contain a space, so let's bypass that |
209 | boost::split(result, cct->_conf->rgw_s3_auth_order, | |
210 | boost::is_any_of(", "), boost::token_compress_on); | |
211 | ||
212 | if (std::any_of(result.begin(), result.end(), | |
11fdf7f2 | 213 | [allowed_auth](std::string_view s) |
1adf2230 AA |
214 | { return allowed_auth.find(s) == allowed_auth.end();})){ |
215 | return default_order; | |
216 | } | |
217 | return result; | |
218 | } | |
219 | ||
31f18b77 | 220 | AWSAuthStrategy(CephContext* const cct, |
1e59de90 TL |
221 | const rgw::auth::ImplicitTenants& implicit_tenant_context, |
222 | rgw::sal::Driver* driver) | |
223 | : driver(driver), | |
31f18b77 | 224 | ver_abstractor(cct), |
d2e6a577 FG |
225 | anonymous_engine(cct, |
226 | static_cast<rgw::auth::LocalApplier::Factory*>(this)), | |
1e59de90 TL |
227 | external_engines(cct, driver, implicit_tenant_context, &ver_abstractor), |
228 | sts_engine(cct, driver, implicit_tenant_context, &ver_abstractor), | |
229 | local_engine(cct, driver, ver_abstractor, | |
7c673cae | 230 | static_cast<rgw::auth::LocalApplier::Factory*>(this)) { |
11fdf7f2 | 231 | /* The anonymous auth. */ |
d2e6a577 FG |
232 | if (AllowAnonAccessT) { |
233 | add_engine(Control::SUFFICIENT, anonymous_engine); | |
234 | } | |
7c673cae | 235 | |
1adf2230 AA |
236 | auto auth_order = parse_auth_order(cct); |
237 | engine_map_t engine_map; | |
11fdf7f2 TL |
238 | |
239 | /* STS Auth*/ | |
240 | if (! sts_engine.is_empty()) { | |
241 | engine_map.insert(std::make_pair("sts", std::cref(sts_engine))); | |
242 | } | |
243 | ||
d2e6a577 | 244 | /* The external auth. */ |
7c673cae | 245 | if (! external_engines.is_empty()) { |
1adf2230 | 246 | engine_map.insert(std::make_pair("external", std::cref(external_engines))); |
7c673cae | 247 | } |
d2e6a577 | 248 | /* The local auth. */ |
7c673cae | 249 | if (cct->_conf->rgw_s3_auth_use_rados) { |
1adf2230 | 250 | engine_map.insert(std::make_pair("local", std::cref(local_engine))); |
7c673cae | 251 | } |
11fdf7f2 | 252 | |
1adf2230 | 253 | add_engines(auth_order, engine_map); |
7c673cae FG |
254 | } |
255 | ||
256 | const char* get_name() const noexcept override { | |
31f18b77 | 257 | return "rgw::auth::s3::AWSAuthStrategy"; |
7c673cae FG |
258 | } |
259 | }; | |
260 | ||
31f18b77 FG |
261 | |
262 | class AWSv4ComplMulti : public rgw::auth::Completer, | |
263 | public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>, | |
264 | public std::enable_shared_from_this<AWSv4ComplMulti> { | |
265 | using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>; | |
11fdf7f2 | 266 | using signing_key_t = sha256_digest_t; |
31f18b77 FG |
267 | |
268 | CephContext* const cct; | |
269 | ||
f67539c2 TL |
270 | const std::string_view date; |
271 | const std::string_view credential_scope; | |
31f18b77 FG |
272 | const signing_key_t signing_key; |
273 | ||
274 | class ChunkMeta { | |
275 | size_t data_offset_in_stream = 0; | |
276 | size_t data_length = 0; | |
277 | std::string signature; | |
278 | ||
279 | ChunkMeta(const size_t data_starts_in_stream, | |
280 | const size_t data_length, | |
f67539c2 | 281 | const std::string_view signature) |
31f18b77 FG |
282 | : data_offset_in_stream(data_starts_in_stream), |
283 | data_length(data_length), | |
f67539c2 | 284 | signature(std::string(signature)) { |
31f18b77 FG |
285 | } |
286 | ||
f67539c2 TL |
287 | explicit ChunkMeta(const std::string_view& signature) |
288 | : signature(std::string(signature)) { | |
31f18b77 FG |
289 | } |
290 | ||
291 | public: | |
292 | static constexpr size_t SIG_SIZE = 64; | |
293 | ||
294 | /* Let's suppose the data length fields can't exceed uint64_t. */ | |
295 | static constexpr size_t META_MAX_SIZE = \ | |
296 | sarrlen("\r\nffffffffffffffff;chunk-signature=") + SIG_SIZE + sarrlen("\r\n"); | |
297 | ||
298 | /* The metadata size of for the last, empty chunk. */ | |
299 | static constexpr size_t META_MIN_SIZE = \ | |
300 | sarrlen("0;chunk-signature=") + SIG_SIZE + sarrlen("\r\n"); | |
301 | ||
302 | /* Detect whether a given stream_pos fits in boundaries of a chunk. */ | |
303 | bool is_new_chunk_in_stream(size_t stream_pos) const; | |
304 | ||
305 | /* Get the remaining data size. */ | |
306 | size_t get_data_size(size_t stream_pos) const; | |
307 | ||
308 | const std::string& get_signature() const { | |
309 | return signature; | |
310 | } | |
311 | ||
312 | /* Factory: create an object representing metadata of first, initial chunk | |
313 | * in a stream. */ | |
f67539c2 | 314 | static ChunkMeta create_first(const std::string_view& seed_signature) { |
31f18b77 FG |
315 | return ChunkMeta(seed_signature); |
316 | } | |
317 | ||
318 | /* Factory: parse a block of META_MAX_SIZE bytes and creates an object | |
319 | * representing non-first chunk in a stream. As the process is sequential | |
320 | * and depends on the previous chunk, caller must pass it. */ | |
321 | static std::pair<ChunkMeta, size_t> create_next(CephContext* cct, | |
322 | ChunkMeta&& prev, | |
323 | const char* metabuf, | |
324 | size_t metabuf_len); | |
325 | } chunk_meta; | |
326 | ||
327 | size_t stream_pos; | |
328 | boost::container::static_vector<char, ChunkMeta::META_MAX_SIZE> parsing_buf; | |
329 | ceph::crypto::SHA256* sha256_hash; | |
330 | std::string prev_chunk_signature; | |
331 | ||
332 | bool is_signature_mismatched(); | |
333 | std::string calc_chunk_signature(const std::string& payload_hash) const; | |
aee94f69 | 334 | size_t recv_chunk(char* buf, size_t max, bool& eof); |
31f18b77 FG |
335 | |
336 | public: | |
337 | /* We need the constructor to be public because of the std::make_shared that | |
338 | * is employed by the create() method. */ | |
339 | AWSv4ComplMulti(const req_state* const s, | |
f67539c2 TL |
340 | std::string_view date, |
341 | std::string_view credential_scope, | |
342 | std::string_view seed_signature, | |
31f18b77 FG |
343 | const signing_key_t& signing_key) |
344 | : io_base_t(nullptr), | |
345 | cct(s->cct), | |
346 | date(std::move(date)), | |
347 | credential_scope(std::move(credential_scope)), | |
348 | signing_key(signing_key), | |
349 | ||
350 | /* The evolving state. */ | |
351 | chunk_meta(ChunkMeta::create_first(seed_signature)), | |
352 | stream_pos(0), | |
353 | sha256_hash(calc_hash_sha256_open_stream()), | |
354 | prev_chunk_signature(std::move(seed_signature)) { | |
355 | } | |
356 | ||
357 | ~AWSv4ComplMulti() { | |
358 | if (sha256_hash) { | |
359 | calc_hash_sha256_close_stream(&sha256_hash); | |
360 | } | |
361 | } | |
362 | ||
363 | /* rgw::io::DecoratedRestfulClient. */ | |
364 | size_t recv_body(char* buf, size_t max) override; | |
365 | ||
366 | /* rgw::auth::Completer. */ | |
11fdf7f2 | 367 | void modify_request_state(const DoutPrefixProvider* dpp, req_state* s_rw) override; |
31f18b77 FG |
368 | bool complete() override; |
369 | ||
370 | /* Factories. */ | |
371 | static cmplptr_t create(const req_state* s, | |
f67539c2 TL |
372 | std::string_view date, |
373 | std::string_view credential_scope, | |
374 | std::string_view seed_signature, | |
31f18b77 FG |
375 | const boost::optional<std::string>& secret_key); |
376 | ||
377 | }; | |
378 | ||
379 | class AWSv4ComplSingle : public rgw::auth::Completer, | |
380 | public rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>, | |
381 | public std::enable_shared_from_this<AWSv4ComplSingle> { | |
382 | using io_base_t = rgw::io::DecoratedRestfulClient<rgw::io::RestfulClient*>; | |
383 | ||
384 | CephContext* const cct; | |
385 | const char* const expected_request_payload_hash; | |
386 | ceph::crypto::SHA256* sha256_hash = nullptr; | |
387 | ||
388 | public: | |
389 | /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). We need | |
390 | * the constructor to be public because of the std::make_shared employed by | |
391 | * the create() method. */ | |
11fdf7f2 | 392 | explicit AWSv4ComplSingle(const req_state* const s); |
31f18b77 FG |
393 | |
394 | ~AWSv4ComplSingle() { | |
395 | if (sha256_hash) { | |
396 | calc_hash_sha256_close_stream(&sha256_hash); | |
397 | } | |
398 | } | |
399 | ||
400 | /* rgw::io::DecoratedRestfulClient. */ | |
401 | size_t recv_body(char* buf, size_t max) override; | |
402 | ||
403 | /* rgw::auth::Completer. */ | |
11fdf7f2 | 404 | void modify_request_state(const DoutPrefixProvider* dpp, req_state* s_rw) override; |
31f18b77 FG |
405 | bool complete() override; |
406 | ||
407 | /* Factories. */ | |
408 | static cmplptr_t create(const req_state* s, | |
409 | const boost::optional<std::string>&); | |
410 | ||
411 | }; | |
412 | ||
7c673cae FG |
413 | } /* namespace s3 */ |
414 | } /* namespace auth */ | |
415 | } /* namespace rgw */ | |
416 | ||
417 | void rgw_create_s3_canonical_header( | |
b3b6e05e | 418 | const DoutPrefixProvider *dpp, |
7c673cae FG |
419 | const char *method, |
420 | const char *content_md5, | |
421 | const char *content_type, | |
422 | const char *date, | |
9f95a23c TL |
423 | const meta_map_t& meta_map, |
424 | const meta_map_t& qs_map, | |
7c673cae FG |
425 | const char *request_uri, |
426 | const std::map<std::string, std::string>& sub_resources, | |
427 | std::string& dest_str); | |
b3b6e05e TL |
428 | bool rgw_create_s3_canonical_header(const DoutPrefixProvider *dpp, |
429 | const req_info& info, | |
7c673cae FG |
430 | utime_t *header_time, /* out */ |
431 | std::string& dest, /* out */ | |
432 | bool qsr); | |
433 | static inline std::tuple<bool, std::string, utime_t> | |
b3b6e05e | 434 | rgw_create_s3_canonical_header(const DoutPrefixProvider *dpp, const req_info& info, const bool qsr) { |
7c673cae FG |
435 | std::string dest; |
436 | utime_t header_time; | |
437 | ||
b3b6e05e | 438 | const bool ok = rgw_create_s3_canonical_header(dpp, info, &header_time, dest, qsr); |
7c673cae FG |
439 | return std::make_tuple(ok, dest, header_time); |
440 | } | |
441 | ||
31f18b77 FG |
442 | namespace rgw { |
443 | namespace auth { | |
444 | namespace s3 { | |
445 | ||
446 | static constexpr char AWS4_HMAC_SHA256_STR[] = "AWS4-HMAC-SHA256"; | |
224ce89b | 447 | static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR[] = "AWS4-HMAC-SHA256-PAYLOAD"; |
31f18b77 FG |
448 | |
449 | static constexpr char AWS4_EMPTY_PAYLOAD_HASH[] = \ | |
450 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; | |
451 | ||
452 | static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH[] = "UNSIGNED-PAYLOAD"; | |
453 | ||
454 | static constexpr char AWS4_STREAMING_PAYLOAD_HASH[] = \ | |
455 | "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"; | |
456 | ||
20effc67 TL |
457 | bool is_non_s3_op(RGWOpType op_type); |
458 | ||
a8e16298 | 459 | int parse_v4_credentials(const req_info& info, /* in */ |
f67539c2 TL |
460 | std::string_view& access_key_id, /* out */ |
461 | std::string_view& credential_scope, /* out */ | |
462 | std::string_view& signedheaders, /* out */ | |
463 | std::string_view& signature, /* out */ | |
464 | std::string_view& date, /* out */ | |
465 | std::string_view& session_token, /* out */ | |
b3b6e05e TL |
466 | const bool using_qs, /* in */ |
467 | const DoutPrefixProvider *dpp); /* in */ | |
11fdf7f2 | 468 | |
20effc67 TL |
469 | string gen_v4_scope(const ceph::real_time& timestamp, |
470 | const string& region, | |
471 | const string& service); | |
472 | ||
11fdf7f2 TL |
473 | static inline bool char_needs_aws4_escaping(const char c, bool encode_slash) |
474 | { | |
475 | if ((c >= 'a' && c <= 'z') || | |
476 | (c >= 'A' && c <= 'Z') || | |
477 | (c >= '0' && c <= '9')) { | |
478 | return false; | |
479 | } | |
480 | ||
481 | switch (c) { | |
482 | case '-': | |
483 | case '_': | |
484 | case '.': | |
485 | case '~': | |
486 | return false; | |
487 | } | |
488 | ||
489 | if (c == '/' && !encode_slash) | |
490 | return false; | |
491 | ||
492 | return true; | |
493 | } | |
494 | ||
495 | static inline std::string aws4_uri_encode(const std::string& src, bool encode_slash) | |
496 | { | |
497 | std::string result; | |
498 | ||
499 | for (const std::string::value_type c : src) { | |
500 | if (char_needs_aws4_escaping(c, encode_slash)) { | |
501 | rgw_uri_escape_char(c, result); | |
502 | } else { | |
503 | result.push_back(c); | |
504 | } | |
505 | } | |
506 | ||
507 | return result; | |
508 | } | |
509 | ||
f67539c2 | 510 | static inline std::string aws4_uri_recode(const std::string_view& src, bool encode_slash) |
11fdf7f2 TL |
511 | { |
512 | std::string decoded = url_decode(src); | |
513 | return aws4_uri_encode(decoded, encode_slash); | |
514 | } | |
31f18b77 FG |
515 | |
516 | static inline std::string get_v4_canonical_uri(const req_info& info) { | |
517 | /* The code should normalize according to RFC 3986 but S3 does NOT do path | |
518 | * normalization that SigV4 typically does. This code follows the same | |
519 | * approach that boto library. See auth.py:canonical_uri(...). */ | |
520 | ||
11fdf7f2 | 521 | std::string canonical_uri = aws4_uri_recode(info.request_uri_aws4, false); |
31f18b77 FG |
522 | |
523 | if (canonical_uri.empty()) { | |
524 | canonical_uri = "/"; | |
525 | } else { | |
526 | boost::replace_all(canonical_uri, "+", "%20"); | |
527 | } | |
528 | ||
529 | return canonical_uri; | |
530 | } | |
531 | ||
20effc67 TL |
532 | static inline std::string gen_v4_canonical_uri(const req_info& info) { |
533 | /* The code should normalize according to RFC 3986 but S3 does NOT do path | |
534 | * normalization that SigV4 typically does. This code follows the same | |
535 | * approach that boto library. See auth.py:canonical_uri(...). */ | |
536 | ||
537 | std::string canonical_uri = aws4_uri_recode(info.request_uri, false); | |
538 | ||
539 | if (canonical_uri.empty()) { | |
540 | canonical_uri = "/"; | |
541 | } else { | |
542 | boost::replace_all(canonical_uri, "+", "%20"); | |
543 | } | |
544 | ||
545 | return canonical_uri; | |
546 | } | |
547 | ||
11fdf7f2 TL |
548 | static inline const string calc_v4_payload_hash(const string& payload) |
549 | { | |
550 | ceph::crypto::SHA256* sha256_hash = calc_hash_sha256_open_stream(); | |
551 | calc_hash_sha256_update_stream(sha256_hash, payload.c_str(), payload.length()); | |
552 | const auto payload_hash = calc_hash_sha256_close_stream(&sha256_hash); | |
553 | return payload_hash; | |
554 | } | |
555 | ||
31f18b77 FG |
556 | static inline const char* get_v4_exp_payload_hash(const req_info& info) |
557 | { | |
11fdf7f2 | 558 | /* In AWSv4 the hash of real, transferred payload IS NOT necessary to form |
31f18b77 FG |
559 | * a Canonical Request, and thus verify a Signature. x-amz-content-sha256 |
560 | * header lets get the information very early -- before seeing first byte | |
561 | * of HTTP body. As a consequence, we can decouple Signature verification | |
562 | * from payload's fingerprint check. */ | |
563 | const char *expected_request_payload_hash = \ | |
564 | info.env->get("HTTP_X_AMZ_CONTENT_SHA256"); | |
565 | ||
566 | if (!expected_request_payload_hash) { | |
567 | /* An HTTP client MUST send x-amz-content-sha256. The single exception | |
568 | * is the case of using the Query Parameters where "UNSIGNED-PAYLOAD" | |
569 | * literals are used for crafting Canonical Request: | |
570 | * | |
571 | * You don't include a payload hash in the Canonical Request, because | |
572 | * when you create a presigned URL, you don't know the payload content | |
573 | * because the URL is used to upload an arbitrary payload. Instead, you | |
574 | * use a constant string UNSIGNED-PAYLOAD. */ | |
575 | expected_request_payload_hash = AWS4_UNSIGNED_PAYLOAD_HASH; | |
576 | } | |
577 | ||
578 | return expected_request_payload_hash; | |
579 | } | |
580 | ||
581 | static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash) | |
582 | { | |
583 | return boost::equals(exp_payload_hash, AWS4_UNSIGNED_PAYLOAD_HASH); | |
584 | } | |
585 | ||
586 | static inline bool is_v4_payload_empty(const req_state* const s) | |
587 | { | |
588 | /* from rfc2616 - 4.3 Message Body | |
589 | * | |
590 | * "The presence of a message-body in a request is signaled by the inclusion | |
591 | * of a Content-Length or Transfer-Encoding header field in the request's | |
592 | * message-headers." */ | |
593 | return s->content_length == 0 && | |
594 | s->info.env->get("HTTP_TRANSFER_ENCODING") == nullptr; | |
595 | } | |
596 | ||
597 | static inline bool is_v4_payload_streamed(const char* const exp_payload_hash) | |
598 | { | |
599 | return boost::equals(exp_payload_hash, AWS4_STREAMING_PAYLOAD_HASH); | |
600 | } | |
601 | ||
602 | std::string get_v4_canonical_qs(const req_info& info, bool using_qs); | |
603 | ||
39ae355f | 604 | std::string gen_v4_canonical_qs(const req_info& info, bool is_non_s3_op); |
20effc67 | 605 | |
aee94f69 TL |
606 | std::string get_v4_canonical_method(const req_state* s); |
607 | ||
31f18b77 FG |
608 | boost::optional<std::string> |
609 | get_v4_canonical_headers(const req_info& info, | |
f67539c2 | 610 | const std::string_view& signedheaders, |
31f18b77 FG |
611 | bool using_qs, |
612 | bool force_boto2_compat); | |
613 | ||
20effc67 TL |
614 | std::string gen_v4_canonical_headers(const req_info& info, |
615 | const std::map<std::string, std::string>& extra_headers, | |
616 | string *signed_hdrs); | |
617 | ||
31f18b77 FG |
618 | extern sha256_digest_t |
619 | get_v4_canon_req_hash(CephContext* cct, | |
f67539c2 | 620 | const std::string_view& http_verb, |
31f18b77 FG |
621 | const std::string& canonical_uri, |
622 | const std::string& canonical_qs, | |
623 | const std::string& canonical_hdrs, | |
f67539c2 | 624 | const std::string_view& signed_hdrs, |
b3b6e05e TL |
625 | const std::string_view& request_payload_hash, |
626 | const DoutPrefixProvider *dpp); | |
31f18b77 FG |
627 | |
628 | AWSEngine::VersionAbstractor::string_to_sign_t | |
629 | get_v4_string_to_sign(CephContext* cct, | |
f67539c2 TL |
630 | const std::string_view& algorithm, |
631 | const std::string_view& request_date, | |
632 | const std::string_view& credential_scope, | |
b3b6e05e TL |
633 | const sha256_digest_t& canonreq_hash, |
634 | const DoutPrefixProvider *dpp); | |
31f18b77 FG |
635 | |
636 | extern AWSEngine::VersionAbstractor::server_signature_t | |
f67539c2 | 637 | get_v4_signature(const std::string_view& credential_scope, |
31f18b77 | 638 | CephContext* const cct, |
f67539c2 | 639 | const std::string_view& secret_key, |
b3b6e05e TL |
640 | const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign, |
641 | const DoutPrefixProvider *dpp); | |
31f18b77 FG |
642 | |
643 | extern AWSEngine::VersionAbstractor::server_signature_t | |
644 | get_v2_signature(CephContext*, | |
645 | const std::string& secret_key, | |
646 | const AWSEngine::VersionAbstractor::string_to_sign_t& string_to_sign); | |
31f18b77 FG |
647 | } /* namespace s3 */ |
648 | } /* namespace auth */ | |
649 | } /* namespace rgw */ |