1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #ifndef CEPH_RGW_AUTH_S3_H
5 #define CEPH_RGW_AUTH_S3_H
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>
17 #include "common/sstring.hh"
18 #include "rgw_common.h"
19 #include "rgw_rest_s3.h"
22 #include "rgw_auth_filters.h"
23 #include "rgw_auth_keystone.h"
30 static constexpr auto RGW_AUTH_GRACE
= std::chrono::minutes
{15};
32 // returns true if the request time is within RGW_AUTH_GRACE of the current time
33 bool is_time_skew_ok(time_t t
);
35 class ExternalAuthStrategy
: public rgw::auth::Strategy
,
36 public rgw::auth::RemoteApplier::Factory
{
37 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t
;
38 RGWRados
* const store
;
39 rgw::auth::ImplicitTenants
& implicit_tenant_context
;
41 using keystone_config_t
= rgw::keystone::CephCtxConfig
;
42 using keystone_cache_t
= rgw::keystone::TokenCache
;
43 using EC2Engine
= rgw::auth::keystone::EC2Engine
;
45 boost::optional
<EC2Engine
> keystone_engine
;
46 LDAPEngine ldap_engine
;
48 aplptr_t
create_apl_remote(CephContext
* const cct
,
49 const req_state
* const s
,
50 rgw::auth::RemoteApplier::acl_strategy_t
&& acl_alg
,
51 const rgw::auth::RemoteApplier::AuthInfo info
53 auto apl
= rgw::auth::add_sysreq(cct
, store
, s
,
54 rgw::auth::RemoteApplier(cct
, store
, std::move(acl_alg
), info
,
55 implicit_tenant_context
,
56 rgw::auth::ImplicitTenants::IMPLICIT_TENANTS_S3
));
57 /* TODO(rzarzynski): replace with static_ptr. */
58 return aplptr_t(new decltype(apl
)(std::move(apl
)));
62 ExternalAuthStrategy(CephContext
* const cct
,
63 RGWRados
* const store
,
64 rgw::auth::ImplicitTenants
& implicit_tenant_context
,
65 AWSEngine::VersionAbstractor
* const ver_abstractor
)
67 implicit_tenant_context(implicit_tenant_context
),
68 ldap_engine(cct
, store
, *ver_abstractor
,
69 static_cast<rgw::auth::RemoteApplier::Factory
*>(this)) {
71 if (cct
->_conf
->rgw_s3_auth_use_keystone
&&
72 ! cct
->_conf
->rgw_keystone_url
.empty()) {
74 keystone_engine
.emplace(cct
, ver_abstractor
,
75 static_cast<rgw::auth::RemoteApplier::Factory
*>(this),
76 keystone_config_t::get_instance(),
77 keystone_cache_t::get_instance
<keystone_config_t
>());
78 add_engine(Control::SUFFICIENT
, *keystone_engine
);
82 if (cct
->_conf
->rgw_s3_auth_use_ldap
&&
83 ! cct
->_conf
->rgw_ldap_uri
.empty()) {
84 add_engine(Control::SUFFICIENT
, ldap_engine
);
88 const char* get_name() const noexcept override
{
89 return "rgw::auth::s3::AWSv2ExternalAuthStrategy";
94 template <class AbstractorT
,
95 bool AllowAnonAccessT
= false>
96 class AWSAuthStrategy
: public rgw::auth::Strategy
,
97 public rgw::auth::LocalApplier::Factory
{
98 typedef rgw::auth::IdentityApplier::aplptr_t aplptr_t
;
100 static_assert(std::is_base_of
<rgw::auth::s3::AWSEngine::VersionAbstractor
,
102 "AbstractorT must be a subclass of rgw::auth::s3::VersionAbstractor");
104 RGWRados
* const store
;
105 AbstractorT ver_abstractor
;
107 S3AnonymousEngine anonymous_engine
;
108 ExternalAuthStrategy external_engines
;
109 LocalEngine local_engine
;
111 aplptr_t
create_apl_local(CephContext
* const cct
,
112 const req_state
* const s
,
113 const RGWUserInfo
& user_info
,
114 const std::string
& subuser
) const override
{
115 auto apl
= rgw::auth::add_sysreq(cct
, store
, s
,
116 rgw::auth::LocalApplier(cct
, user_info
, subuser
));
117 /* TODO(rzarzynski): replace with static_ptr. */
118 return aplptr_t(new decltype(apl
)(std::move(apl
)));
122 AWSAuthStrategy(CephContext
* const cct
,
123 rgw::auth::ImplicitTenants
& implicit_tenant_context
,
124 RGWRados
* const store
)
127 anonymous_engine(cct
,
128 static_cast<rgw::auth::LocalApplier::Factory
*>(this)),
129 external_engines(cct
, store
, implicit_tenant_context
, &ver_abstractor
),
130 local_engine(cct
, store
, ver_abstractor
,
131 static_cast<rgw::auth::LocalApplier::Factory
*>(this)) {
132 /* The anynoymous auth. */
133 if (AllowAnonAccessT
) {
134 add_engine(Control::SUFFICIENT
, anonymous_engine
);
137 /* The external auth. */
138 Control local_engine_mode
;
139 if (! external_engines
.is_empty()) {
140 add_engine(Control::SUFFICIENT
, external_engines
);
142 local_engine_mode
= Control::FALLBACK
;
144 local_engine_mode
= Control::SUFFICIENT
;
147 /* The local auth. */
148 if (cct
->_conf
->rgw_s3_auth_use_rados
) {
149 add_engine(local_engine_mode
, local_engine
);
153 const char* get_name() const noexcept override
{
154 return "rgw::auth::s3::AWSAuthStrategy";
159 class AWSv4ComplMulti
: public rgw::auth::Completer
,
160 public rgw::io::DecoratedRestfulClient
<rgw::io::RestfulClient
*>,
161 public std::enable_shared_from_this
<AWSv4ComplMulti
> {
162 using io_base_t
= rgw::io::DecoratedRestfulClient
<rgw::io::RestfulClient
*>;
163 using signing_key_t
= std::array
<unsigned char,
164 CEPH_CRYPTO_HMACSHA256_DIGESTSIZE
>;
166 CephContext
* const cct
;
168 const boost::string_view date
;
169 const boost::string_view credential_scope
;
170 const signing_key_t signing_key
;
173 size_t data_offset_in_stream
= 0;
174 size_t data_length
= 0;
175 std::string signature
;
177 ChunkMeta(const size_t data_starts_in_stream
,
178 const size_t data_length
,
179 const boost::string_ref signature
)
180 : data_offset_in_stream(data_starts_in_stream
),
181 data_length(data_length
),
182 signature(signature
.to_string()) {
185 ChunkMeta(const boost::string_view
& signature
)
186 : signature(signature
.to_string()) {
190 static constexpr size_t SIG_SIZE
= 64;
192 /* Let's suppose the data length fields can't exceed uint64_t. */
193 static constexpr size_t META_MAX_SIZE
= \
194 sarrlen("\r\nffffffffffffffff;chunk-signature=") + SIG_SIZE
+ sarrlen("\r\n");
196 /* The metadata size of for the last, empty chunk. */
197 static constexpr size_t META_MIN_SIZE
= \
198 sarrlen("0;chunk-signature=") + SIG_SIZE
+ sarrlen("\r\n");
200 /* Detect whether a given stream_pos fits in boundaries of a chunk. */
201 bool is_new_chunk_in_stream(size_t stream_pos
) const;
203 /* Get the remaining data size. */
204 size_t get_data_size(size_t stream_pos
) const;
206 const std::string
& get_signature() const {
210 /* Factory: create an object representing metadata of first, initial chunk
212 static ChunkMeta
create_first(const boost::string_view
& seed_signature
) {
213 return ChunkMeta(seed_signature
);
216 /* Factory: parse a block of META_MAX_SIZE bytes and creates an object
217 * representing non-first chunk in a stream. As the process is sequential
218 * and depends on the previous chunk, caller must pass it. */
219 static std::pair
<ChunkMeta
, size_t> create_next(CephContext
* cct
,
226 boost::container::static_vector
<char, ChunkMeta::META_MAX_SIZE
> parsing_buf
;
227 ceph::crypto::SHA256
* sha256_hash
;
228 std::string prev_chunk_signature
;
230 bool is_signature_mismatched();
231 std::string
calc_chunk_signature(const std::string
& payload_hash
) const;
234 /* We need the constructor to be public because of the std::make_shared that
235 * is employed by the create() method. */
236 AWSv4ComplMulti(const req_state
* const s
,
237 boost::string_view date
,
238 boost::string_view credential_scope
,
239 boost::string_view seed_signature
,
240 const signing_key_t
& signing_key
)
241 : io_base_t(nullptr),
243 date(std::move(date
)),
244 credential_scope(std::move(credential_scope
)),
245 signing_key(signing_key
),
247 /* The evolving state. */
248 chunk_meta(ChunkMeta::create_first(seed_signature
)),
250 sha256_hash(calc_hash_sha256_open_stream()),
251 prev_chunk_signature(std::move(seed_signature
)) {
256 calc_hash_sha256_close_stream(&sha256_hash
);
260 /* rgw::io::DecoratedRestfulClient. */
261 size_t recv_body(char* buf
, size_t max
) override
;
263 /* rgw::auth::Completer. */
264 void modify_request_state(req_state
* s_rw
) override
;
265 bool complete() override
;
268 static cmplptr_t
create(const req_state
* s
,
269 boost::string_view date
,
270 boost::string_view credential_scope
,
271 boost::string_view seed_signature
,
272 const boost::optional
<std::string
>& secret_key
);
276 class AWSv4ComplSingle
: public rgw::auth::Completer
,
277 public rgw::io::DecoratedRestfulClient
<rgw::io::RestfulClient
*>,
278 public std::enable_shared_from_this
<AWSv4ComplSingle
> {
279 using io_base_t
= rgw::io::DecoratedRestfulClient
<rgw::io::RestfulClient
*>;
281 CephContext
* const cct
;
282 const char* const expected_request_payload_hash
;
283 ceph::crypto::SHA256
* sha256_hash
= nullptr;
286 /* Defined in rgw_auth_s3.cc because of get_v4_exp_payload_hash(). We need
287 * the constructor to be public because of the std::make_shared employed by
288 * the create() method. */
289 AWSv4ComplSingle(const req_state
* const s
);
291 ~AWSv4ComplSingle() {
293 calc_hash_sha256_close_stream(&sha256_hash
);
297 /* rgw::io::DecoratedRestfulClient. */
298 size_t recv_body(char* buf
, size_t max
) override
;
300 /* rgw::auth::Completer. */
301 void modify_request_state(req_state
* s_rw
) override
;
302 bool complete() override
;
305 static cmplptr_t
create(const req_state
* s
,
306 const boost::optional
<std::string
>&);
311 } /* namespace auth */
312 } /* namespace rgw */
314 void rgw_create_s3_canonical_header(
316 const char *content_md5
,
317 const char *content_type
,
319 const std::map
<std::string
, std::string
>& meta_map
,
320 const char *request_uri
,
321 const std::map
<std::string
, std::string
>& sub_resources
,
322 std::string
& dest_str
);
323 bool rgw_create_s3_canonical_header(const req_info
& info
,
324 utime_t
*header_time
, /* out */
325 std::string
& dest
, /* out */
327 static inline std::tuple
<bool, std::string
, utime_t
>
328 rgw_create_s3_canonical_header(const req_info
& info
, const bool qsr
) {
332 const bool ok
= rgw_create_s3_canonical_header(info
, &header_time
, dest
, qsr
);
333 return std::make_tuple(ok
, dest
, header_time
);
340 static constexpr char AWS4_HMAC_SHA256_STR
[] = "AWS4-HMAC-SHA256";
341 static constexpr char AWS4_HMAC_SHA256_PAYLOAD_STR
[] = "AWS4-HMAC-SHA256-PAYLOAD";
343 static constexpr char AWS4_EMPTY_PAYLOAD_HASH
[] = \
344 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
346 static constexpr char AWS4_UNSIGNED_PAYLOAD_HASH
[] = "UNSIGNED-PAYLOAD";
348 static constexpr char AWS4_STREAMING_PAYLOAD_HASH
[] = \
349 "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
351 int parse_credentials(const req_info
& info
, /* in */
352 boost::string_view
& access_key_id
, /* out */
353 boost::string_view
& credential_scope
, /* out */
354 boost::string_view
& signedheaders
, /* out */
355 boost::string_view
& signature
, /* out */
356 boost::string_view
& date
, /* out */
357 bool& using_qs
); /* out */
359 static inline std::string
get_v4_canonical_uri(const req_info
& info
) {
360 /* The code should normalize according to RFC 3986 but S3 does NOT do path
361 * normalization that SigV4 typically does. This code follows the same
362 * approach that boto library. See auth.py:canonical_uri(...). */
364 std::string canonical_uri
= info
.request_uri_aws4
;
366 if (canonical_uri
.empty()) {
369 boost::replace_all(canonical_uri
, "+", "%20");
372 return canonical_uri
;
375 static inline const char* get_v4_exp_payload_hash(const req_info
& info
)
377 /* In AWSv4 the hash of real, transfered payload IS NOT necessary to form
378 * a Canonical Request, and thus verify a Signature. x-amz-content-sha256
379 * header lets get the information very early -- before seeing first byte
380 * of HTTP body. As a consequence, we can decouple Signature verification
381 * from payload's fingerprint check. */
382 const char *expected_request_payload_hash
= \
383 info
.env
->get("HTTP_X_AMZ_CONTENT_SHA256");
385 if (!expected_request_payload_hash
) {
386 /* An HTTP client MUST send x-amz-content-sha256. The single exception
387 * is the case of using the Query Parameters where "UNSIGNED-PAYLOAD"
388 * literals are used for crafting Canonical Request:
390 * You don't include a payload hash in the Canonical Request, because
391 * when you create a presigned URL, you don't know the payload content
392 * because the URL is used to upload an arbitrary payload. Instead, you
393 * use a constant string UNSIGNED-PAYLOAD. */
394 expected_request_payload_hash
= AWS4_UNSIGNED_PAYLOAD_HASH
;
397 return expected_request_payload_hash
;
400 static inline bool is_v4_payload_unsigned(const char* const exp_payload_hash
)
402 return boost::equals(exp_payload_hash
, AWS4_UNSIGNED_PAYLOAD_HASH
);
405 static inline bool is_v4_payload_empty(const req_state
* const s
)
407 /* from rfc2616 - 4.3 Message Body
409 * "The presence of a message-body in a request is signaled by the inclusion
410 * of a Content-Length or Transfer-Encoding header field in the request's
411 * message-headers." */
412 return s
->content_length
== 0 &&
413 s
->info
.env
->get("HTTP_TRANSFER_ENCODING") == nullptr;
416 static inline bool is_v4_payload_streamed(const char* const exp_payload_hash
)
418 return boost::equals(exp_payload_hash
, AWS4_STREAMING_PAYLOAD_HASH
);
421 std::string
get_v4_canonical_qs(const req_info
& info
, bool using_qs
);
423 boost::optional
<std::string
>
424 get_v4_canonical_headers(const req_info
& info
,
425 const boost::string_view
& signedheaders
,
427 bool force_boto2_compat
);
429 extern sha256_digest_t
430 get_v4_canon_req_hash(CephContext
* cct
,
431 const boost::string_view
& http_verb
,
432 const std::string
& canonical_uri
,
433 const std::string
& canonical_qs
,
434 const std::string
& canonical_hdrs
,
435 const boost::string_view
& signed_hdrs
,
436 const boost::string_view
& request_payload_hash
);
438 AWSEngine::VersionAbstractor::string_to_sign_t
439 get_v4_string_to_sign(CephContext
* cct
,
440 const boost::string_view
& algorithm
,
441 const boost::string_view
& request_date
,
442 const boost::string_view
& credential_scope
,
443 const sha256_digest_t
& canonreq_hash
);
445 extern AWSEngine::VersionAbstractor::server_signature_t
446 get_v4_signature(const boost::string_view
& credential_scope
,
447 CephContext
* const cct
,
448 const boost::string_view
& secret_key
,
449 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
);
451 extern AWSEngine::VersionAbstractor::server_signature_t
452 get_v2_signature(CephContext
*,
453 const std::string
& secret_key
,
454 const AWSEngine::VersionAbstractor::string_to_sign_t
& string_to_sign
);
457 } /* namespace auth */
458 } /* namespace rgw */