1 1 | /*
|
2 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 3 | * SPDX-License-Identifier: Apache-2.0
|
4 4 | */
|
5 5 |
|
6 6 | use std::time::{Duration, SystemTime};
|
7 7 |
|
8 8 | use aws_config::timeout::TimeoutConfig;
|
9 9 | use aws_config::Region;
|
10 + | use aws_runtime::user_agent::test_util::{
|
11 + | assert_ua_contains_metric_values, assert_ua_does_not_contain_metric_values,
|
12 + | };
|
10 13 | use aws_sdk_s3::config::endpoint::{EndpointFuture, Params, ResolveEndpoint};
|
11 14 | use aws_sdk_s3::config::{Builder, Credentials};
|
12 15 | use aws_sdk_s3::presigning::PresigningConfig;
|
13 16 | use aws_sdk_s3::primitives::SdkBody;
|
14 17 | use aws_sdk_s3::types::ChecksumAlgorithm;
|
15 18 | use aws_sdk_s3::{Client, Config};
|
16 19 | use aws_smithy_http_client::test_util::dvr::ReplayingClient;
|
17 20 | use aws_smithy_http_client::test_util::{capture_request, ReplayEvent, StaticReplayClient};
|
18 21 | use aws_smithy_runtime::test_util::capture_test_logs::capture_test_logs;
|
19 22 | use aws_smithy_types::endpoint::Endpoint;
|
20 23 | use http_1x::Uri;
|
21 24 |
|
22 25 | async fn test_client<F>(update_builder: F) -> Client
|
23 26 | where
|
24 27 | F: Fn(Builder) -> Builder,
|
25 28 | {
|
26 29 | let sdk_config = aws_config::from_env().region("us-west-2").load().await;
|
27 30 | let config = Config::from(&sdk_config).to_builder().with_test_defaults();
|
28 31 | aws_sdk_s3::Client::from_conf(update_builder(config).build())
|
29 32 | }
|
30 33 |
|
31 34 | #[tokio::test]
|
32 35 | async fn create_session_request_should_not_include_x_amz_s3session_token() {
|
33 36 | let (http_client, request) = capture_request(None);
|
34 37 | // There was a bug where a regular SigV4 session token was overwritten by an express session token
|
35 38 | // even for CreateSession API request.
|
36 39 | // To exercise that code path, it is important to include credentials with a session token below.
|
37 40 | let conf = Config::builder()
|
38 41 | .http_client(http_client)
|
39 42 | .region(Region::new("us-west-2"))
|
40 43 | .credentials_provider(::aws_credential_types::Credentials::for_tests_with_session_token())
|
41 44 | .build();
|
42 45 | let client = Client::from_conf(conf);
|
43 46 |
|
44 47 | let _ = client
|
45 48 | .list_objects_v2()
|
46 49 | .bucket("s3express-test-bucket--usw2-az1--x-s3")
|
47 50 | .send()
|
48 51 | .await;
|
49 52 |
|
50 53 | let req = request.expect_request();
|
51 54 | assert!(
|
52 55 | req.headers().get("x-amz-create-session-mode").is_none(),
|
53 56 | "`x-amz-create-session-mode` should not appear in headers of the first request when an express bucket is specified"
|
54 57 | );
|
55 58 | assert!(req.headers().get("x-amz-security-token").is_some());
|
56 59 | assert!(req.headers().get("x-amz-s3session-token").is_none());
|
60 + |
|
61 + | // The first request uses regular SigV4 credentials (for CreateSession), not S3 Express credentials,
|
62 + | // so metric "J" should NOT be present yet. It will appear on subsequent requests that use S3 Express credentials.
|
63 + | let user_agent = req.headers().get("x-amz-user-agent").unwrap();
|
64 + | assert_ua_does_not_contain_metric_values(user_agent, &["J"]);
|
57 65 | }
|
58 66 |
|
59 67 | #[tokio::test]
|
60 68 | async fn mixed_auths() {
|
61 69 | let _logs = capture_test_logs();
|
62 70 |
|
63 71 | let http_client = ReplayingClient::from_file("tests/data/express/mixed-auths.json").unwrap();
|
64 72 | let client = test_client(|b| b.http_client(http_client.clone())).await;
|
65 73 |
|
66 74 | // A call to an S3 Express bucket where we should see two request/response pairs,
|
67 75 | // one for the `create_session` API and the other for `list_objects_v2` in S3 Express bucket.
|
68 76 | let result = client
|
69 77 | .list_objects_v2()
|
70 78 | .bucket("s3express-test-bucket--usw2-az1--x-s3")
|
71 79 | .send()
|
72 80 | .await;
|
73 81 | dbg!(result).expect("success");
|
74 82 |
|
75 83 | // A call to a regular bucket, and request headers should not contain `x-amz-s3session-token`.
|
76 84 | let result = client
|
77 85 | .list_objects_v2()
|
78 86 | .bucket("regular-test-bucket")
|
79 87 | .send()
|
80 88 | .await;
|
81 89 | dbg!(result).expect("success");
|
82 90 |
|
83 91 | // A call to another S3 Express bucket where we should again see two request/response pairs,
|
84 92 | // one for the `create_session` API and the other for `list_objects_v2` in S3 Express bucket.
|
85 93 | let result = client
|
86 94 | .list_objects_v2()
|
87 95 | .bucket("s3express-test-bucket-2--usw2-az3--x-s3")
|
88 96 | .send()
|
89 97 | .await;
|
90 98 | dbg!(result).expect("success");
|
91 99 |
|
92 100 | // This call should be an identity cache hit for the first S3 Express bucket,
|
93 101 | // thus no HTTP request should be sent to the `create_session` API.
|
94 102 | let result = client
|
95 103 | .list_objects_v2()
|
96 104 | .bucket("s3express-test-bucket--usw2-az1--x-s3")
|
97 105 | .send()
|
98 106 | .await;
|
99 107 | dbg!(result).expect("success");
|
100 108 |
|
101 109 | http_client
|
102 110 | .validate_body_and_headers(Some(&["x-amz-s3session-token"]), "application/xml")
|
103 111 | .await
|
104 112 | .unwrap();
|
113 + |
|
114 + | // Verify that requests using S3 Express credentials contain metric "J"
|
115 + | let requests = http_client.take_requests();
|
116 + | let s3express_requests: Vec<_> = requests
|
117 + | .iter()
|
118 + | .filter(|req| req.headers().get("x-amz-s3session-token").is_some())
|
119 + | .collect();
|
120 + |
|
121 + | assert!(!s3express_requests.is_empty(), "Should have S3 Express requests");
|
122 + | for req in s3express_requests {
|
123 + | let user_agent = req.headers().get("x-amz-user-agent").unwrap();
|
124 + | assert_ua_contains_metric_values(user_agent, &["J"]);
|
125 + | }
|
105 126 | }
|
106 127 |
|
107 128 | fn create_session_request() -> http_1x::Request<SdkBody> {
|
108 129 | http_1x::Request::builder()
|
109 130 | .uri("https://s3express-test-bucket--usw2-az1--x-s3.s3express-usw2-az1.us-west-2.amazonaws.com/?session")
|
110 131 | .method("GET")
|
111 132 | .body(SdkBody::empty())
|
112 133 | .unwrap()
|
113 134 | }
|
114 135 |
|
259 280 | ]);
|
260 281 | let client = test_client(|b| b.http_client(http_client.clone())).await;
|
261 282 |
|
262 283 | let _ = client
|
263 284 | .delete_objects()
|
264 285 | .bucket("s3express-test-bucket--usw2-az1--x-s3")
|
265 286 | .send()
|
266 287 | .await;
|
267 288 |
|
268 289 | let checksum_headers: Vec<_> = http_client
|
269 - | .actual_requests()
|
290 + | .take_requests()
|
270 291 | .last()
|
271 292 | .unwrap()
|
272 293 | .headers()
|
273 294 | .iter()
|
274 295 | .filter(|(key, _)| key.starts_with("x-amz-checksum"))
|
275 296 | .collect();
|
276 297 |
|
277 298 | assert_eq!(1, checksum_headers.len());
|
278 299 | assert_eq!("x-amz-checksum-crc32", checksum_headers[0].0);
|
279 300 | http_client.assert_requests_match(&[""]);
|
280 301 | }
|
281 302 |
|
282 303 | #[tokio::test]
|
283 304 | async fn default_checksum_should_be_none() {
|
284 305 | let http_client = StaticReplayClient::new(vec![
|
285 306 | ReplayEvent::new(create_session_request(), create_session_response()),
|
286 307 | ReplayEvent::new(
|
287 308 | operation_request_with_checksum("test?x-id=PutObject", None),
|
288 309 | response_ok(),
|
289 310 | ),
|
290 311 | ]);
|
291 312 | let client = test_client(|b| b.http_client(http_client.clone())).await;
|
292 313 |
|
293 314 | let _ = client
|
294 315 | .put_object()
|
295 316 | .bucket("s3express-test-bucket--usw2-az1--x-s3")
|
296 317 | .key("test")
|
297 318 | .body(SdkBody::empty().into())
|
298 319 | .send()
|
299 320 | .await;
|
300 321 |
|
301 322 | http_client.assert_requests_match(&[""]);
|
302 323 |
|
303 324 | let mut all_checksums = ChecksumAlgorithm::values()
|
304 325 | .iter()
|
305 326 | .map(|checksum| format!("amz-checksum-{}", checksum.to_lowercase()))
|
306 327 | .chain(std::iter::once("content-md5".to_string()));
|
307 328 |
|
308 329 | assert!(!all_checksums.any(|checksum| http_client
|
309 - | .actual_requests()
|
330 + | .take_requests()
|
310 331 | .any(|req| req.headers().iter().any(|(key, _)| key == checksum))));
|
311 332 | }
|
312 333 |
|
313 334 | #[tokio::test]
|
314 335 | async fn disable_s3_express_session_auth_at_service_client_level() {
|
315 336 | let (http_client, request) = capture_request(None);
|
316 337 | let conf = Config::builder()
|
317 338 | .http_client(http_client)
|
318 339 | .region(Region::new("us-west-2"))
|
319 340 | .with_test_defaults()
|
320 341 | .disable_s3_express_session_auth(true)
|
321 342 | .build();
|
322 343 | let client = Client::from_conf(conf);
|
323 344 |
|
324 345 | let _ = client
|
325 346 | .list_objects_v2()
|
326 347 | .bucket("s3express-test-bucket--usw2-az1--x-s3")
|
327 348 | .send()
|
328 349 | .await;
|
329 350 |
|
330 351 | let req = request.expect_request();
|
331 352 | assert!(
|
332 353 | req.headers().get("x-amz-create-session-mode").is_none(),
|
333 354 | "x-amz-create-session-mode should not appear in headers when S3 Express session auth is disabled"
|
334 355 | );
|
356 + |
|
357 + | // Verify that the User-Agent does NOT contain the S3ExpressBucket metric "J" when session auth is disabled
|
358 + | let user_agent = req.headers().get("x-amz-user-agent").unwrap();
|
359 + | assert_ua_does_not_contain_metric_values(user_agent, &["J"]);
|
335 360 | }
|
336 361 |
|
337 362 | #[tokio::test]
|
338 363 | async fn disable_s3_express_session_auth_at_operation_level() {
|
339 364 | let (http_client, request) = capture_request(None);
|
340 365 | let conf = Config::builder()
|
341 366 | .http_client(http_client)
|
342 367 | .region(Region::new("us-west-2"))
|
343 368 | .with_test_defaults()
|
344 369 | .build();
|