1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
12 #include <boost/algorithm/string.hpp>
13 #include <boost/container/static_vector.hpp>
15 #include "common/sstring.hh"
16 #include "rgw_common.h"
17 #include "rgw_rest_s3.h"
19 #include "rgw_auth_filters.h"
20 #include "rgw_auth_keystone.h"
27 static constexpr auto RGW_AUTH_GRACE
= std::chrono::minutes
{15};
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
);
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
;
37 rgw::sal::Driver
* driver
;
38 const rgw::auth::ImplicitTenants
& implicit_tenant_context
;
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
) const override
{
46 auto apl
= rgw::auth::add_sysreq(cct
, driver
, s
,
47 rgw::auth::RemoteApplier(cct
, driver
, std::move(acl_alg
), info
,
48 implicit_tenant_context
,
49 rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3
));
50 return aplptr_t(new decltype(apl
)(std::move(apl
)));
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
,
57 const std::optional
<uint32_t>& perm_mask
,
58 const std::string
& access_key_id
) const override
{
59 auto apl
= rgw::auth::add_sysreq(cct
, driver
, s
,
60 rgw::auth::LocalApplier(cct
, user_info
, subuser
, perm_mask
, access_key_id
));
61 return aplptr_t(new decltype(apl
)(std::move(apl
)));
64 aplptr_t
create_apl_role(CephContext
* const cct
,
65 const req_state
* const s
,
66 const rgw::auth::RoleApplier::Role
& role
,
67 const rgw::auth::RoleApplier::TokenAttrs
& token_attrs
) const override
{
68 auto apl
= rgw::auth::add_sysreq(cct
, driver
, s
,
69 rgw::auth::RoleApplier(cct
, role
, token_attrs
));
70 return aplptr_t(new decltype(apl
)(std::move(apl
)));
74 STSAuthStrategy(CephContext
* const cct
,
75 rgw::sal::Driver
* driver
,
76 const rgw::auth::ImplicitTenants
& implicit_tenant_context
,
77 AWSEngine::VersionAbstractor
* const ver_abstractor
)
79 implicit_tenant_context(implicit_tenant_context
),
80 sts_engine(cct
, driver
, *ver_abstractor
,
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
);
89 const char* get_name() const noexcept override
{
90 return "rgw::auth::s3::STSAuthStrategy";
94 class ExternalAuthStrategy
: public rgw::auth::Strategy
,
95 public rgw::auth::RemoteApplier::Factory
{
96 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t
;
97 rgw::sal::Driver
* driver
;
98 const rgw::auth::ImplicitTenants
& implicit_tenant_context
;
100 using keystone_config_t
= rgw::keystone::CephCtxConfig
;
101 using keystone_cache_t
= rgw::keystone::TokenCache
;
102 using secret_cache_t
= rgw::auth::keystone::SecretCache
;
103 using EC2Engine
= rgw::auth::keystone::EC2Engine
;
105 boost::optional
<EC2Engine
> keystone_engine
;
106 LDAPEngine ldap_engine
;
108 aplptr_t
create_apl_remote(CephContext
* const cct
,
109 const req_state
* const s
,
110 rgw::auth::RemoteApplier::acl_strategy_t
&& acl_alg
,
111 const rgw::auth::RemoteApplier::AuthInfo
&info
) const override
{
112 auto apl
= rgw::auth::add_sysreq(cct
, driver
, s
,
113 rgw::auth::RemoteApplier(cct
, driver
, std::move(acl_alg
), info
,
114 implicit_tenant_context
,
115 rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3
));
116 /* TODO(rzarzynski): replace with static_ptr. */
117 return aplptr_t(new decltype(apl
)(std::move(apl
)));
121 ExternalAuthStrategy(CephContext
* const cct
,
122 rgw::sal::Driver
* driver
,
123 const rgw::auth::ImplicitTenants
& implicit_tenant_context
,
124 AWSEngine::VersionAbstractor
* const ver_abstractor
)
126 implicit_tenant_context(implicit_tenant_context
),
127 ldap_engine(cct
, driver
, *ver_abstractor
,
128 static_cast<rgw::auth::RemoteApplier::Factory
*>(this)) {
130 if (cct
->_conf
->rgw_s3_auth_use_keystone
&&
131 ! cct
->_conf
->rgw_keystone_url
.empty()) {
133 keystone_engine
.emplace(cct
, ver_abstractor
,
134 static_cast<rgw::auth::RemoteApplier::Factory
*>(this),
135 keystone_config_t::get_instance(),
136 keystone_cache_t::get_instance
<keystone_config_t
>(),
137 secret_cache_t::get_instance());
138 add_engine(Control::SUFFICIENT
, *keystone_engine
);
142 if (ldap_engine
.valid()) {
143 add_engine(Control::SUFFICIENT
, ldap_engine
);
147 const char* get_name() const noexcept override
{
148 return "rgw::auth::s3::AWSv2ExternalAuthStrategy";
153 template <class AbstractorT
,
154 bool AllowAnonAccessT
= false>
155 class AWSAuthStrategy
: public rgw::auth::Strategy
,
156 public rgw::auth::LocalApplier::Factory
{
157 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t
;
159 static_assert(std::is_base_of
<rgw::auth::s3::AWSEngine::VersionAbstractor
,
161 "AbstractorT must be a subclass of rgw::auth::s3::VersionAbstractor");
163 rgw::sal::Driver
* driver
;
164 AbstractorT ver_abstractor
;
166 S3AnonymousEngine anonymous_engine
;
167 ExternalAuthStrategy external_engines
;
168 STSAuthStrategy sts_engine
;
169 LocalEngine local_engine
;
171 aplptr_t
create_apl_local(CephContext
* const cct
,
172 const req_state
* const s
,
173 const RGWUserInfo
& user_info
,
174 const std::string
& subuser
,
175 const std::optional
<uint32_t>& perm_mask
,
176 const std::string
& access_key_id
) const override
{
177 auto apl
= rgw::auth::add_sysreq(cct
, driver
, s
,
178 rgw::auth::LocalApplier(cct
, user_info
, subuser
, perm_mask
, access_key_id
));
179 /* TODO(rzarzynski): replace with static_ptr. */
180 return aplptr_t(new decltype(apl
)(std::move(apl
)));
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
)
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
;
195 if (const auto kv
= eng_map
.find(eng
);
196 kv
!= eng_map
.end()) {
197 add_engine(ctrl_flag
, kv
->second
);
202 auto parse_auth_order(CephContext
* const cct
)
204 std::vector
<std::string
> result
;
206 const std::set
<std::string_view
> allowed_auth
= { "sts", "external", "local" };
207 std::vector
<std::string
> default_order
= { "sts", "external", "local" };
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
);
212 if (std::any_of(result
.begin(), result
.end(),
213 [allowed_auth
](std::string_view s
)
214 { return allowed_auth
.find(s
) == allowed_auth
.end();})){
215 return default_order
;
220 AWSAuthStrategy(CephContext
* const cct
,
221 const rgw::auth::ImplicitTenants
& implicit_tenant_context
,
222 rgw::sal::Driver
* driver
)
225 anonymous_engine(cct
,
226 static_cast<rgw::auth::LocalApplier::Factory
*>(this)),
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
,
230 static_cast<rgw::auth::LocalApplier::Factory
*>(this)) {
231 /* The anonymous auth. */
232 if (AllowAnonAccessT
) {
233 add_engine(Control::SUFFICIENT
, anonymous_engine
);
236 auto auth_order
= parse_auth_order(cct
);
237 engine_map_t engine_map
;
240 if (! sts_engine
.is_empty()) {
241 engine_map
.insert(std::make_pair("sts", std::cref(sts_engine
)));
244 /* The external auth. */
245 if (! external_engines
.is_empty()) {
246 engine_map
.insert(std::make_pair("external", std::cref(external_engines
)));
248 /* The local auth. */
249 if (cct
->_conf
->rgw_s3_auth_use_rados
) {
250 engine_map
.insert(std::make_pair("local", std::cref(local_engine
)));
253 add_engines(auth_order
, engine_map
);
256 const char* get_name() const noexcept override
{
257 return "rgw::auth::s3::AWSAuthStrategy";
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
*>;
266 using signing_key_t
= sha256_digest_t
;
268 CephContext
* const cct
;
270 const std::string_view date
;
271 const std::string_view credential_scope
;
272 const signing_key_t signing_key
;
275 size_t data_offset_in_stream
= 0;
276 size_t data_length
= 0;
277 std::string signature
;
279 ChunkMeta(const size_t data_starts_in_stream
,
280 const size_t data_length
,
281 const std::string_view signature
)
282 : data_offset_in_stream(data_starts_in_stream
),
283 data_length(data_length
),
284 signature(std::string(signature
)) {
287 explicit ChunkMeta(const std::string_view
& signature
)
288 : signature(std::string(signature
)) {
292 static constexpr size_t SIG_SIZE
= 64;
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");
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");
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;
305 /* Get the remaining data size. */
306 size_t get_data_size(size_t stream_pos
) const;
308 const std::string
& get_signature() const {
312 /* Factory: create an object representing metadata of first, initial chunk
314 static ChunkMeta
create_first(const std::string_view
& seed_signature
) {
315 return ChunkMeta(seed_signature
);
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
,
328 boost::container::static_vector
<char, ChunkMeta::META_MAX_SIZE
> parsing_buf
;
329 ceph::crypto::SHA256
* sha256_hash
;
330 std::string prev_chunk_signature
;
332 bool is_signature_mismatched();
333 std::string
calc_chunk_signature(const std::string
& payload_hash
) const;
334 size_t recv_chunk(char* buf
, size_t max
, bool& eof
);
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
,
340 std::string_view date
,
341 std::string_view credential_scope
,
342 std::string_view seed_signature
,
343 const signing_key_t
& signing_key
)
344 : io_base_t(nullptr),
346 date(std::move(date
)),
347 credential_scope(std::move(credential_scope
)),
348 signing_key(signing_key
),
350 /* The evolving state. */
351 chunk_meta(ChunkMeta::create_first(seed_signature
)),
353 sha256_hash(calc_hash_sha256_open_stream()),
354 prev_chunk_signature(std::move(seed_signature
)) {
359 calc_hash_sha256_close_stream(&sha256_hash
);
363 /* rgw::io::DecoratedRestfulClient. */
364 size_t recv_body(char* buf
, size_t max
) override
;
366 /* rgw::auth::Completer. */
367 void modify_request_state(const DoutPrefixProvider
* dpp
, req_state
* s_rw
) override
;
368 bool complete() override
;
371 static cmplptr_t
create(const req_state
* s
,
372 std::string_view date
,
373 std::string_view credential_scope
,
374 std::string_view seed_signature
,
375 const boost::optional
<std::string
>& secret_key
);
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
*>;
384 CephContext
* const cct
;
385 const char* const expected_request_payload_hash
;
386 ceph::crypto::SHA256
* sha256_hash
= nullptr;
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. */
392 explicit AWSv4ComplSingle(const req_state
* const s
);
394 ~AWSv4ComplSingle() {
396 calc_hash_sha256_close_stream(&sha256_hash
);
400 /* rgw::io::DecoratedRestfulClient. */
401 size_t recv_body(char* buf
, size_t max
) override
;
403 /* rgw::auth::Completer. */
404 void modify_request_state(const DoutPrefixProvider
* dpp
, req_state
* s_rw
) override
;
405 bool complete() override
;
408 static cmplptr_t
create(const req_state
* s
,
409 const boost::optional
<std::string
>&);
414 } /* namespace auth */
415 } /* namespace rgw */
417 void rgw_create_s3_canonical_header(
418 const DoutPrefixProvider
*dpp
,
420 const char *content_md5
,
421 const char *content_type
,
423 const meta_map_t
& meta_map
,
424 const meta_map_t
& qs_map
,
425 const char *request_uri
,
426 const std::map
<std::string
, std::string
>& sub_resources
,
427 std::string
& dest_str
);
428 bool rgw_create_s3_canonical_header(const DoutPrefixProvider
*dpp
,
429 const req_info
& info
,
430 utime_t
*header_time
, /* out */
431 std::string
& dest
, /* out */
433 static inline std::tuple
<bool, std::string
, utime_t
>
434 rgw_create_s3_canonical_header(const DoutPrefixProvider
*dpp
, const req_info
& info
, const bool qsr
) {
438 const bool ok
= rgw_create_s3_canonical_header(dpp
, info
, &header_time
, dest
, qsr
);
439 return std::make_tuple(ok
, dest
, header_time
);
446 static constexpr char AWS4_HMAC_SHA256_STR
[] = "AWS4-HMAC-SHA256";
447 static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR
[] = "AWS4-HMAC-SHA256-PAYLOAD";
449 static constexpr char AWS4_EMPTY_PAYLOAD_HASH
[] = \
450 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
452 static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH
[] = "UNSIGNED-PAYLOAD";
454 static constexpr char AWS4_STREAMING_PAYLOAD_HASH
[] = \
455 "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
457 bool is_non_s3_op(RGWOpType op_type
);
459 int parse_v4_credentials(const req_info
& info
, /* in */
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 */
466 const bool using_qs
, /* in */
467 const DoutPrefixProvider
*dpp
); /* in */
469 string
gen_v4_scope(const ceph::real_time
& timestamp
,
470 const string
& region
,
471 const string
& service
);
473 static inline bool char_needs_aws4_escaping(const char c
, bool encode_slash
)
475 if ((c
>= 'a' && c
<= 'z') ||
476 (c
>= 'A' && c
<= 'Z') ||
477 (c
>= '0' && c
<= '9')) {
489 if (c
== '/' && !encode_slash
)
495 static inline std::string
aws4_uri_encode(const std::string
& src
, bool encode_slash
)
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
);
510 static inline std::string
aws4_uri_recode(const std::string_view
& src
, bool encode_slash
)
512 std::string decoded
= url_decode(src
);
513 return aws4_uri_encode(decoded
, encode_slash
);
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(...). */
521 std::string canonical_uri
= aws4_uri_recode(info
.request_uri_aws4
, false);
523 if (canonical_uri
.empty()) {
526 boost::replace_all(canonical_uri
, "+", "%20");
529 return canonical_uri
;
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(...). */
537 std::string canonical_uri
= aws4_uri_recode(info
.request_uri
, false);
539 if (canonical_uri
.empty()) {
542 boost::replace_all(canonical_uri
, "+", "%20");
545 return canonical_uri
;
548 static inline const string
calc_v4_payload_hash(const string
& payload
)
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
);
556 static inline const char* get_v4_exp_payload_hash(const req_info
& info
)
558 /* In AWSv4 the hash of real, transferred payload IS NOT necessary to form
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");
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:
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
;
578 return expected_request_payload_hash
;
581 static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash
)
583 return boost::equals(exp_payload_hash
, AWS4_UNSIGNED_PAYLOAD_HASH
);
586 static inline bool is_v4_payload_empty(const req_state
* const s
)
588 /* from rfc2616 - 4.3 Message Body
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;
597 static inline bool is_v4_payload_streamed(const char* const exp_payload_hash
)
599 return boost::equals(exp_payload_hash
, AWS4_STREAMING_PAYLOAD_HASH
);
602 std::string
get_v4_canonical_qs(const req_info
& info
, bool using_qs
);
604 std::string
gen_v4_canonical_qs(const req_info
& info
, bool is_non_s3_op
);
606 std::string
get_v4_canonical_method(const req_state
* s
);
608 boost::optional
<std::string
>
609 get_v4_canonical_headers(const req_info
& info
,
610 const std::string_view
& signedheaders
,
612 bool force_boto2_compat
);
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
);
618 extern sha256_digest_t
619 get_v4_canon_req_hash(CephContext
* cct
,
620 const std::string_view
& http_verb
,
621 const std::string
& canonical_uri
,
622 const std::string
& canonical_qs
,
623 const std::string
& canonical_hdrs
,
624 const std::string_view
& signed_hdrs
,
625 const std::string_view
& request_payload_hash
,
626 const DoutPrefixProvider
*dpp
);
628 AWSEngine::VersionAbstractor::string_to_sign_t
629 get_v4_string_to_sign(CephContext
* cct
,
630 const std::string_view
& algorithm
,
631 const std::string_view
& request_date
,
632 const std::string_view
& credential_scope
,
633 const sha256_digest_t
& canonreq_hash
,
634 const DoutPrefixProvider
*dpp
);
636 extern AWSEngine::VersionAbstractor::server_signature_t
637 get_v4_signature(const std::string_view
& credential_scope
,
638 CephContext
* const cct
,
639 const std::string_view
& secret_key
,
640 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
,
641 const DoutPrefixProvider
*dpp
);
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
);
648 } /* namespace auth */
649 } /* namespace rgw */