Skip to content

Commit b30b063

Browse files
authored
Integrate TimeSource into the orchestrator and middleware (breaking change warning is spurious) (#2728)
## Motivation and Context - #2262 - #2087 - #2707 This adds `TimeSource` in SDK and service configs so we can effectively control time when executing requests with the SDK at the client level. _note:_ the breaking change is in a trait implementation of a struct we're going to delete, not a real breaking change ## Description - Add `SharedTimeSource` and use it in SdkConfig / `aws-config` / <service>::Config - Wire up the signer and other uses of `SystemTime::now()` that I could find - track down broken tests ## Testing - [x] various unit tests that all still pass ## Checklist <!--- If a checkbox below is not applicable, then please DELETE it rather than leaving it unchecked --> - [x] I have updated `CHANGELOG.next.toml` if I made changes to the smithy-rs codegen or runtime crates - [x] I have updated `CHANGELOG.next.toml` if I made changes to the AWS SDK, generated SDK code, or SDK runtime crates ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent cf292d5 commit b30b063

File tree

37 files changed

+325
-141
lines changed

37 files changed

+325
-141
lines changed

CHANGELOG.next.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,27 @@ references = ["smithy-rs#2539"]
8181
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" }
8282
author = "david-perez"
8383

84+
[[smithy-rs]]
85+
message = "Time is now controlled by the `TimeSource` trait. This facilitates testing as well as use cases like WASM where `SystemTime::now()` is not supported."
86+
references = ["smithy-rs#2728", "smithy-rs#2262", "aws-sdk-rust#2087"]
87+
meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client" }
88+
author = "rcoh"
89+
90+
[[smithy-rs]]
91+
message = "The property bag type for Time is now `SharedTimeSource`, not `SystemTime`. If your code relies on setting request time, use `aws_smithy_async::time::SharedTimeSource`."
92+
references = ["smithy-rs#2728", "smithy-rs#2262", "aws-sdk-rust#2087"]
93+
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" }
94+
author = "rcoh"
95+
96+
[[aws-sdk-rust]]
97+
message = "Time is now controlled by the `TimeSource` trait. This facilitates testing as well as use cases like WASM where `SystemTime::now()` is not supported."
98+
references = ["smithy-rs#2728", "smithy-rs#2262", "aws-sdk-rust#2087"]
99+
meta = { "breaking" = false, "tada" = false, "bug" = false }
100+
author = "rcoh"
101+
84102
[[smithy-rs]]
85103
message = "Bump dependency on `lambda_http` by `aws-smithy-http-server` to 0.8.0. This version of `aws-smithy-http-server` is only guaranteed to be compatible with 0.8.0, or semver-compatible versions of 0.8.0 of the `lambda_http` crate. It will not work with versions prior to 0.8.0 _at runtime_, making requests to your smithy-rs service unroutable, so please make sure you're running your service in a compatible configuration"
86104
author = "david-perez"
87105
references = ["smithy-rs#2676", "smithy-rs#2685"]
88106
meta = { "breaking" = true, "tada" = false, "bug" = false }
107+

aws/rust-runtime/aws-config/external-types.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ allowed_external_types = [
99
"aws_credential_types::provider::SharedCredentialsProvider",
1010
"aws_sdk_sts::types::_policy_descriptor_type::PolicyDescriptorType",
1111
"aws_smithy_async::rt::sleep::AsyncSleep",
12+
"aws_smithy_async::time::TimeSource",
1213
"aws_smithy_client::bounds::SmithyConnector",
1314
"aws_smithy_client::conns::default_connector::default_connector",
1415
"aws_smithy_client::erase::DynConnector",

aws/rust-runtime/aws-config/src/imds/client/token.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
use crate::imds::client::error::{ImdsError, TokenError, TokenErrorKind};
1818
use crate::imds::client::ImdsResponseRetryClassifier;
1919
use aws_credential_types::cache::ExpiringCache;
20-
use aws_credential_types::time_source::TimeSource;
2120
use aws_http::user_agent::UserAgentStage;
2221
use aws_smithy_async::rt::sleep::AsyncSleep;
22+
use aws_smithy_async::time::SharedTimeSource;
2323
use aws_smithy_client::erase::DynConnector;
2424
use aws_smithy_client::retry;
2525
use aws_smithy_http::body::SdkBody;
@@ -65,7 +65,7 @@ pub(super) struct TokenMiddleware {
6565
client: Arc<aws_smithy_client::Client<DynConnector, MapRequestLayer<UserAgentStage>>>,
6666
token_parser: GetTokenResponseHandler,
6767
token: ExpiringCache<Token, ImdsError>,
68-
time_source: TimeSource,
68+
time_source: SharedTimeSource,
6969
endpoint: Uri,
7070
token_ttl: Duration,
7171
}
@@ -79,7 +79,7 @@ impl Debug for TokenMiddleware {
7979
impl TokenMiddleware {
8080
pub(super) fn new(
8181
connector: DynConnector,
82-
time_source: TimeSource,
82+
time_source: SharedTimeSource,
8383
endpoint: Uri,
8484
token_ttl: Duration,
8585
retry_config: retry::Config,
@@ -170,7 +170,7 @@ impl AsyncMapRequest for TokenMiddleware {
170170

171171
#[derive(Clone)]
172172
struct GetTokenResponseHandler {
173-
time: TimeSource,
173+
time: SharedTimeSource,
174174
}
175175

176176
impl ParseStrictResponse for GetTokenResponseHandler {

aws/rust-runtime/aws-config/src/imds/credentials.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::imds::client::LazyClient;
1414
use crate::json_credentials::{parse_json_credentials, JsonCredentials, RefreshableCredentials};
1515
use crate::provider_config::ProviderConfig;
1616
use aws_credential_types::provider::{self, error::CredentialsError, future, ProvideCredentials};
17-
use aws_credential_types::time_source::TimeSource;
1817
use aws_credential_types::Credentials;
18+
use aws_smithy_async::time::SharedTimeSource;
1919
use aws_types::os_shim_internal::Env;
2020
use std::borrow::Cow;
2121
use std::error::Error as StdError;
@@ -53,7 +53,7 @@ pub struct ImdsCredentialsProvider {
5353
client: LazyClient,
5454
env: Env,
5555
profile: Option<String>,
56-
time_source: TimeSource,
56+
time_source: SharedTimeSource,
5757
last_retrieved_credentials: Arc<RwLock<Option<Credentials>>>,
5858
}
5959

@@ -390,7 +390,10 @@ mod test {
390390
.build();
391391
let creds = provider.provide_credentials().await.expect("valid creds");
392392
// The expiry should be equal to what is originally set (==2021-09-21T04:16:53Z).
393-
assert!(creds.expiry() == UNIX_EPOCH.checked_add(Duration::from_secs(1632197813)));
393+
assert_eq!(
394+
creds.expiry(),
395+
UNIX_EPOCH.checked_add(Duration::from_secs(1632197813))
396+
);
394397
connection.assert_requests_match(&[]);
395398

396399
// There should not be logs indicating credentials are extended for stability.

aws/rust-runtime/aws-config/src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ mod loader {
155155
use aws_credential_types::cache::CredentialsCache;
156156
use aws_credential_types::provider::{ProvideCredentials, SharedCredentialsProvider};
157157
use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep};
158+
use aws_smithy_async::time::{SharedTimeSource, TimeSource};
158159
use aws_smithy_client::http_connector::HttpConnector;
159160
use aws_smithy_types::retry::RetryConfig;
160161
use aws_smithy_types::timeout::TimeoutConfig;
@@ -192,6 +193,7 @@ mod loader {
192193
profile_files_override: Option<ProfileFiles>,
193194
use_fips: Option<bool>,
194195
use_dual_stack: Option<bool>,
196+
time_source: Option<SharedTimeSource>,
195197
}
196198

197199
impl ConfigLoader {
@@ -262,6 +264,12 @@ mod loader {
262264
self
263265
}
264266

267+
/// Set the time source used for tasks like signing requests
268+
pub fn time_source(mut self, time_source: impl TimeSource + 'static) -> Self {
269+
self.time_source = Some(SharedTimeSource::new(time_source));
270+
self
271+
}
272+
265273
/// Override the [`HttpConnector`] for this [`ConfigLoader`]. The connector will be used when
266274
/// sending operations. This **does not set** the HTTP connector used by config providers.
267275
/// To change that connector, use [ConfigLoader::configure].
@@ -563,7 +571,9 @@ mod loader {
563571
.unwrap_or_else(|| HttpConnector::ConnectorFn(Arc::new(default_connector)));
564572

565573
let credentials_cache = self.credentials_cache.unwrap_or_else(|| {
566-
let mut builder = CredentialsCache::lazy_builder().time_source(conf.time_source());
574+
let mut builder = CredentialsCache::lazy_builder().time_source(
575+
aws_credential_types::time_source::TimeSource::shared(conf.time_source()),
576+
);
567577
builder.set_sleep(conf.sleep());
568578
builder.into_credentials_cache()
569579
});
@@ -588,12 +598,15 @@ mod loader {
588598
SharedCredentialsProvider::new(builder.build().await)
589599
};
590600

601+
let ts = self.time_source.unwrap_or_default();
602+
591603
let mut builder = SdkConfig::builder()
592604
.region(region)
593605
.retry_config(retry_config)
594606
.timeout_config(timeout_config)
595607
.credentials_cache(credentials_cache)
596608
.credentials_provider(credentials_provider)
609+
.time_source(ts)
597610
.http_connector(http_connector);
598611

599612
builder.set_app_name(app_name);

aws/rust-runtime/aws-config/src/profile/credentials/exec.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::web_identity_token::{StaticConfiguration, WebIdentityTokenCredentials
1414
use aws_credential_types::provider::{self, error::CredentialsError, ProvideCredentials};
1515
use aws_sdk_sts::config::{Builder as StsConfigBuilder, Credentials};
1616
use aws_sdk_sts::Client as StsClient;
17+
use aws_smithy_async::time::SharedTimeSource;
1718
use std::fmt::Debug;
1819
use std::sync::Arc;
1920

@@ -22,6 +23,7 @@ pub(super) struct AssumeRoleProvider {
2223
role_arn: String,
2324
external_id: Option<String>,
2425
session_name: Option<String>,
26+
time_source: SharedTimeSource,
2527
}
2628

2729
impl AssumeRoleProvider {
@@ -35,11 +37,9 @@ impl AssumeRoleProvider {
3537
.credentials_provider(input_credentials)
3638
.build();
3739
let client = StsClient::from_conf(config);
38-
let session_name = &self
39-
.session_name
40-
.as_ref()
41-
.cloned()
42-
.unwrap_or_else(|| sts::util::default_session_name("assume-role-from-profile"));
40+
let session_name = &self.session_name.as_ref().cloned().unwrap_or_else(|| {
41+
sts::util::default_session_name("assume-role-from-profile", self.time_source.now())
42+
});
4343
let assume_role_creds = client
4444
.assume_role()
4545
.role_arn(&self.role_arn)
@@ -97,7 +97,12 @@ impl ProviderChain {
9797
web_identity_token_file: web_identity_token_file.into(),
9898
role_arn: role_arn.to_string(),
9999
session_name: session_name.map(|sess| sess.to_string()).unwrap_or_else(
100-
|| sts::util::default_session_name("web-identity-token-profile"),
100+
|| {
101+
sts::util::default_session_name(
102+
"web-identity-token-profile",
103+
provider_config.time_source().now(),
104+
)
105+
},
101106
),
102107
})
103108
.configure(provider_config)
@@ -140,6 +145,7 @@ impl ProviderChain {
140145
role_arn: role_arn.role_arn.into(),
141146
external_id: role_arn.external_id.map(|id| id.into()),
142147
session_name: role_arn.session_name.map(|id| id.into()),
148+
time_source: provider_config.time_source(),
143149
}
144150
})
145151
.collect();

aws/rust-runtime/aws-config/src/provider_config.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
use aws_credential_types::time_source::TimeSource;
99
use aws_smithy_async::rt::sleep::{default_async_sleep, AsyncSleep};
10+
use aws_smithy_async::time::SharedTimeSource;
1011
use aws_smithy_client::erase::DynConnector;
1112
use aws_smithy_types::error::display::DisplayErrorContext;
1213
use aws_types::os_shim_internal::{Env, Fs};
@@ -38,7 +39,7 @@ use crate::profile::{ProfileFileLoadError, ProfileSet};
3839
pub struct ProviderConfig {
3940
env: Env,
4041
fs: Fs,
41-
time_source: TimeSource,
42+
time_source: SharedTimeSource,
4243
connector: HttpConnector,
4344
sleep: Option<Arc<dyn AsyncSleep>>,
4445
region: Option<Region>,
@@ -72,7 +73,7 @@ impl Default for ProviderConfig {
7273
Self {
7374
env: Env::default(),
7475
fs: Fs::default(),
75-
time_source: TimeSource::default(),
76+
time_source: SharedTimeSource::default(),
7677
connector,
7778
sleep: default_async_sleep(),
7879
region: None,
@@ -90,7 +91,6 @@ impl ProviderConfig {
9091
/// Unlike [`ProviderConfig::empty`] where `env` and `fs` will use their non-mocked implementations,
9192
/// this method will use an empty mock environment and an empty mock file system.
9293
pub fn no_configuration() -> Self {
93-
use aws_credential_types::time_source::TestingTimeSource;
9494
use std::collections::HashMap;
9595
use std::time::UNIX_EPOCH;
9696
let fs = Fs::from_raw_map(HashMap::new());
@@ -100,7 +100,7 @@ impl ProviderConfig {
100100
profile_files: ProfileFiles::default(),
101101
env,
102102
fs,
103-
time_source: TimeSource::testing(&TestingTimeSource::new(UNIX_EPOCH)),
103+
time_source: SharedTimeSource::new(UNIX_EPOCH),
104104
connector: HttpConnector::Prebuilt(None),
105105
sleep: None,
106106
region: None,
@@ -140,7 +140,7 @@ impl ProviderConfig {
140140
ProviderConfig {
141141
env: Env::default(),
142142
fs: Fs::default(),
143-
time_source: TimeSource::default(),
143+
time_source: SharedTimeSource::default(),
144144
connector: HttpConnector::Prebuilt(None),
145145
sleep: None,
146146
region: None,
@@ -179,7 +179,7 @@ impl ProviderConfig {
179179
}
180180

181181
#[allow(dead_code)]
182-
pub(crate) fn time_source(&self) -> TimeSource {
182+
pub(crate) fn time_source(&self) -> SharedTimeSource {
183183
self.time_source.clone()
184184
}
185185

@@ -293,7 +293,7 @@ impl ProviderConfig {
293293
#[doc(hidden)]
294294
pub fn with_time_source(self, time_source: TimeSource) -> Self {
295295
ProviderConfig {
296-
time_source,
296+
time_source: SharedTimeSource::new(time_source),
297297
..self
298298
}
299299
}

aws/rust-runtime/aws-config/src/sts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ impl crate::provider_config::ProviderConfig {
2222
.http_connector(expect_connector(self.connector(&Default::default())))
2323
.retry_config(RetryConfig::standard())
2424
.region(self.region())
25+
.time_source(self.time_source())
2526
.credentials_cache(CredentialsCache::no_caching());
2627
builder.set_sleep_impl(self.sleep());
2728
builder

aws/rust-runtime/aws-config/src/sts/assume_role.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,14 @@ impl AssumeRoleProviderBuilder {
211211
let mut config = aws_sdk_sts::Config::builder()
212212
.credentials_cache(credentials_cache)
213213
.credentials_provider(provider)
214+
.time_source(conf.time_source())
214215
.region(self.region.clone())
215216
.http_connector(expect_connector(conf.connector(&Default::default())));
216217
config.set_sleep_impl(conf.sleep());
217218

218-
let session_name = self
219-
.session_name
220-
.unwrap_or_else(|| super::util::default_session_name("assume-role-provider"));
219+
let session_name = self.session_name.unwrap_or_else(|| {
220+
super::util::default_session_name("assume-role-provider", conf.time_source().now())
221+
});
221222

222223
let sts_client = StsClient::from_conf(config.build());
223224
let fluent_builder = sts_client

aws/rust-runtime/aws-config/src/sts/util.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use aws_credential_types::provider::{self, error::CredentialsError};
77
use aws_credential_types::Credentials as AwsCredentials;
88
use aws_sdk_sts::types::Credentials as StsCredentials;
99

10+
use aws_smithy_async::time::TimeSource;
1011
use std::convert::TryFrom;
1112
use std::time::{SystemTime, UNIX_EPOCH};
1213

@@ -45,9 +46,7 @@ pub(crate) fn into_credentials(
4546
/// STS Assume Role providers MUST assign a name to their generated session. When a user does not
4647
/// provide a name for the session, the provider will choose a name composed of a base + a timestamp,
4748
/// e.g. `profile-file-provider-123456789`
48-
pub(crate) fn default_session_name(base: &str) -> String {
49-
let now = SystemTime::now()
50-
.duration_since(UNIX_EPOCH)
51-
.expect("post epoch");
49+
pub(crate) fn default_session_name(base: &str, ts: SystemTime) -> String {
50+
let now = ts.now().duration_since(UNIX_EPOCH).expect("post epoch");
5251
format!("{}-{}", base, now.as_millis())
5352
}

0 commit comments

Comments
 (0)