diff --git a/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt b/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt index 1c7c60e5bbc..963c52aec4c 100644 --- a/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt +++ b/AWSSDKSwiftCLI/Sources/AWSSDKSwiftCLI/Resources/Package.Base.txt @@ -130,6 +130,7 @@ private var runtimeTargets: [Target] { .SmithyIdentity, .SmithyRetriesAPI, .SmithyRetries, + .SmithyTimestamps, .AWSSDKCommon, .AWSSDKHTTPAuth, .AWSSDKChecksums, diff --git a/Package.swift b/Package.swift index b64b2e1afac..a96ee71504c 100644 --- a/Package.swift +++ b/Package.swift @@ -574,6 +574,7 @@ private var runtimeTargets: [Target] { .SmithyIdentity, .SmithyRetriesAPI, .SmithyRetries, + .SmithyTimestamps, .AWSSDKCommon, .AWSSDKHTTPAuth, .AWSSDKChecksums, diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/ClockSkew/AWSClockSkewProvider.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/ClockSkew/AWSClockSkewProvider.swift new file mode 100644 index 00000000000..5d3feef5474 --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/ClockSkew/AWSClockSkewProvider.swift @@ -0,0 +1,104 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import struct Foundation.TimeInterval +import typealias ClientRuntime.ClockSkewProvider +import protocol ClientRuntime.ServiceError +import class SmithyHTTPAPI.HTTPRequest +import class SmithyHTTPAPI.HTTPResponse +@_spi(SmithyTimestamps) import struct SmithyTimestamps.TimestampFormatter + +public enum AWSClockSkewProvider { + private static var absoluteThreshold: TimeInterval { 300.0 } // clock skew of < 5 minutes is not compensated + private static var changeThreshold: TimeInterval { 60.0 } // changes to clock skew of < 1 minute are ignored + + public static func provider() -> ClockSkewProvider { + return clockSkew(request:response:error:previous:) + } + + @Sendable + private static func clockSkew( + request: HTTPRequest, + response: HTTPResponse, + error: Error, + previous: TimeInterval? + ) -> TimeInterval? { + // Check if this error could be the result of clock skew. + // If not, leave the current clock skew value unchanged. + guard isAClockSkewError(request: request, error: error) else { return previous } + + // Get the new clock skew value based on server & client times. + let new = newClockSkew(request: request, response: response, error: error) + + if let new, let previous { + // Update clock skew if it's changed by at least the change threshold + // Updating clock skew for insignificant changes in value will result + // in retry when not likely to succeed + return abs(new - previous) > changeThreshold ? new : previous + } else { + // If previous was nil but new is non-nil, return new. + // If previous was non-nil but new is nil, return nil. + // If previous and new are both nil, return nil. + return new + } + } + + private static func isAClockSkewError(request: HTTPRequest, error: Error) -> Bool { + // Get the error code, which is a cue that clock skew is the cause of the error + guard let code = (error as? ServiceError)?.errorCode else { return false } + + // Check the error code to see if this error could be due to clock skew + // If not, fail fast to prevent having to parse server datetime (slow) + return isDefiniteClockSkewError(code: code) || isProbableClockSkewError(code: code, request: request) + } + + private static func isDefiniteClockSkewError(code: String) -> Bool { + definiteClockSkewErrorCodes.contains(code) + } + + private static func isProbableClockSkewError(code: String, request: HTTPRequest) -> Bool { + // Certain S3 HEAD methods will return generic HTTP 403 errors when the cause of the + // failure is clock skew. To accommodate, check clock skew when the method is HEAD + probableClockSkewErrorCodes.contains(code) || request.method == .head + } + + private static func newClockSkew( + request: HTTPRequest, + response: HTTPResponse, + error: Error + ) -> TimeInterval? { + // Get the datetime that the request was signed at. + // If not available, clock skew can't be determined. + // This should always be set when signing with sigv4 & sigv4a. + guard let clientDate = request.signedAt else { return nil } + + // Need a server Date (from the HTTP response headers) to calculate clock skew. + // If not available, return no clock skew. + // This header should always be included on AWS service responses. + guard let httpDateString = response.headers.value(for: "Date") else { return nil } + guard let serverDate = TimestampFormatter(format: .httpDate).date(from: httpDateString) else { return nil } + + // Calculate & return clock skew if more than the threshold, else return nil. + let clockSkew = serverDate.timeIntervalSince(clientDate) + return abs(clockSkew) > absoluteThreshold ? clockSkew : nil + } +} + +// These error codes indicate that the cause of the failure was clock skew. +private let definiteClockSkewErrorCodes: Set = [ + "RequestTimeTooSkewed", + "RequestExpired", + "RequestInTheFuture", +] + +// These error codes indicate that a possible cause of the failure was clock skew. +// So, when these are received, check/set clock skew & retry to see if that helps. +private let probableClockSkewErrorCodes: Set = [ + "InvalidSignatureException", + "AuthFailure", + "SignatureDoesNotMatch", +] diff --git a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Customizations/AuthTokenGenerator.swift b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Customizations/AuthTokenGenerator.swift index b378e4264a5..5c10d5316ea 100644 --- a/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Customizations/AuthTokenGenerator.swift +++ b/Sources/Core/AWSClientRuntime/Sources/AWSClientRuntime/Customizations/AuthTokenGenerator.swift @@ -65,6 +65,8 @@ public class AuthTokenGenerator { requestBuilder.withQueryItem(URIQueryItem(name: "Action", value: "connect")) requestBuilder.withQueryItem(URIQueryItem(name: "DBUser", value: username)) + let signedAt = Date() + let signingConfig = AWSSigningConfig( credentials: self.awsCredentialIdentity, expiration: expiration, @@ -74,7 +76,7 @@ public class AuthTokenGenerator { shouldNormalizeURIPath: true, omitSessionToken: false ), - date: Date(), + date: signedAt, service: "rds-db", region: region, signatureType: .requestQueryParams, @@ -83,7 +85,8 @@ public class AuthTokenGenerator { let signedRequest = await AWSSigV4Signer().sigV4SignedRequest( requestBuilder: requestBuilder, - signingConfig: signingConfig + signingConfig: signingConfig, + signedAt: signedAt ) guard let presignedURL = signedRequest?.destination.url else { @@ -119,6 +122,8 @@ public class AuthTokenGenerator { let actionQueryItemValue = isForAdmin ? "DbConnectAdmin" : "DbConnect" requestBuilder.withQueryItem(URIQueryItem(name: "Action", value: actionQueryItemValue)) + let signedAt = Date() + let signingConfig = AWSSigningConfig( credentials: self.awsCredentialIdentity, expiration: expiration, @@ -128,7 +133,7 @@ public class AuthTokenGenerator { shouldNormalizeURIPath: true, omitSessionToken: false ), - date: Date(), + date: signedAt, service: "dsql", region: region, signatureType: .requestQueryParams, @@ -137,7 +142,8 @@ public class AuthTokenGenerator { let signedRequest = await AWSSigV4Signer().sigV4SignedRequest( requestBuilder: requestBuilder, - signingConfig: signingConfig + signingConfig: signingConfig, + signedAt: signedAt ) guard let presignedURL = signedRequest?.destination.url else { diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/ClockSkew/AWSClockSkewProviderTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/ClockSkew/AWSClockSkewProviderTests.swift new file mode 100644 index 00000000000..41bb5b06207 --- /dev/null +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/ClockSkew/AWSClockSkewProviderTests.swift @@ -0,0 +1,171 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import AWSClientRuntime +import XCTest +import Smithy +import ClientRuntime +import SmithyHTTPAPI +@_spi(SmithyTimestamps) import SmithyTimestamps + +class AWSClockSkewProviderTests: XCTestCase { + + // MARK: - nil previous clock skew + + func test_clockSkewError_returnsNilWhenClientAndServerTimeAreTheSame() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.definite + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_clockSkewError_returnsNilWhenClientAndServerTimeAreDifferentByLessThanThreshold() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:35:26.000 GMT" // +30 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.definite + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_clockSkewError_returnsIntervalWhenClientAndServerTimeAreDifferentByMoreThanThreshold() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:44:56.000 GMT" // server + 600 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.definite + XCTAssertEqual(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew), -600.0) + } + + func test_nonClockSkewError_returnsNilWhenClientAndServerTimeAreTheSame() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_nonClockSkewError_returnsNilWhenClientAndServerTimeAreDifferentByLessThanThreshold() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:35:26.000 GMT" // +30 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_nonClockSkewError_returnsNilWhenClientAndServerTimeAreDifferentByMoreThanThreshold() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:36:26.000 GMT" // +90 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_headRequest_returnsNilWhenClientAndServerTimeAreTheSame() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.head).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_headRequest_returnsNilWhenClientAndServerTimeAreDifferentByLessThanThreshold() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:35:26.000 GMT" // +30 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.head).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertNil(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew)) + } + + func test_headRequest_returnsIntervalWhenClientAndServerTimeAreDifferentByMoreThanThreshold() { + let previousClockSkew: TimeInterval? = nil + let client = "Sun, 02 Jan 2000 20:44:56.000 GMT" // server + 600 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.head).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertEqual(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew), -600.0) + } + + // MARK: - Non-nil previous clock skew + + func test_nonNilPrevious_returnsNilWhenNewClockSkewIsNil() { + let previousClockSkew: TimeInterval = -400.0 + let client = "Sun, 02 Jan 2000 20:34:56.000 GMT" // server + 0 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.definite + XCTAssertEqual(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew), nil) + } + + func test_nonNilPrevious_returnsPreviousWhenClientAndServerTimeAreDifferentByLessThanThreshold() { + let previousClockSkew: TimeInterval = -400.0 + let client = "Sun, 02 Jan 2000 20:40:56.000 GMT" // server + 360 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.definite + XCTAssertEqual(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew), previousClockSkew) + } + + func test_nonNilPrevious_returnsNewWhenClientAndServerTimeAreDifferentByMoreThanThreshold() { + let previousClockSkew: TimeInterval = -400.0 + let client = "Sun, 02 Jan 2000 20:44:56.000 GMT" // server + 600 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.definite + XCTAssertEqual(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew), -600.0) + } + + func test_nonNilPrevious_returnsPreviousWhenErrorIsNotAClockSkewError() { + let previousClockSkew: TimeInterval = -400.0 + let client = "Sun, 02 Jan 2000 20:44:56.000 GMT" // server + 600 seconds + let server = "Sun, 02 Jan 2000 20:34:56.000 GMT" + let clientDate = TimestampFormatter(format: .httpDate).date(from: client)! + let request = HTTPRequestBuilder().withMethod(.get).withSignedAt(clientDate).build() + let response = HTTPResponse(headers: Headers(["Date": server]), body: ByteStream.noStream, statusCode: .badRequest) + let error = ClockSkewTestError.notDueToClockSkew + XCTAssertEqual(AWSClockSkewProvider.provider()(request, response, error, previousClockSkew), previousClockSkew) + } +} + +private struct ClockSkewTestError: Error, ServiceError { + static var definite: Self { .init(typeName: "RequestTimeTooSkewed") } + static var notDueToClockSkew: Self { .init(typeName: "NotAClockSkewError") } + + var typeName: String? + var message: String? { "" } +} diff --git a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Retry/AWSRetryIntegrationTests.swift b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Retry/AWSRetryIntegrationTests.swift index 34f42a5e62c..2c0bb95b30c 100644 --- a/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Retry/AWSRetryIntegrationTests.swift +++ b/Sources/Core/AWSClientRuntime/Tests/AWSClientRuntimeTests/Retry/AWSRetryIntegrationTests.swift @@ -57,7 +57,13 @@ final class RetryIntegrationTests: XCTestCase { .attributes(context) .retryErrorInfoProvider(DefaultRetryErrorInfoProvider.errorInfo(for:)) .retryStrategy(subject) - .deserialize({ _, _ in TestOutputResponse() }) + .deserialize({ response, _ in + if response.statusCode == .ok { + return TestOutputResponse() + } else { + throw TestHTTPError(statusCode: response.statusCode) + } + }) .executeRequest(next) builder.interceptors.add(AmzSdkInvocationIdMiddleware()) builder.interceptors.add(AmzSdkRequestMiddleware(maxRetries: subject.options.maxRetriesBase)) @@ -229,9 +235,10 @@ private class TestOutputHandler: ExecuteRequest { // Return either a successful response or a HTTP error, depending on the directions in the test step. switch testStep.response { case .success: - return HTTPResponse() + return HTTPResponse(statusCode: .ok) case .httpError(let statusCode): - throw TestHTTPError(statusCode: statusCode) + let httpStatusCode = HTTPStatusCode(rawValue: statusCode)! + return HTTPResponse(statusCode: httpStatusCode) } } @@ -310,9 +317,8 @@ private class TestOutputHandler: ExecuteRequest { private struct TestHTTPError: HTTPError, Error { var httpResponse: HTTPResponse - init(statusCode: Int) { - guard let statusCodeValue = HTTPStatusCode(rawValue: statusCode) else { fatalError("Unrecognized HTTP code") } - self.httpResponse = HTTPResponse(statusCode: statusCodeValue) + init(statusCode: HTTPStatusCode) { + self.httpResponse = HTTPResponse(statusCode: statusCode) } } diff --git a/Sources/Core/AWSSDKHTTPAuth/Sources/AWSSDKHTTPAuth/AWSSigV4Signer.swift b/Sources/Core/AWSSDKHTTPAuth/Sources/AWSSDKHTTPAuth/AWSSigV4Signer.swift index 82396acafbc..a21a5c0eb7b 100644 --- a/Sources/Core/AWSSDKHTTPAuth/Sources/AWSSDKHTTPAuth/AWSSigV4Signer.swift +++ b/Sources/Core/AWSSDKHTTPAuth/Sources/AWSSDKHTTPAuth/AWSSigV4Signer.swift @@ -56,7 +56,15 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { ) } - var signingConfig = try constructSigningConfig(identity: identity, signingProperties: signingProperties) + // Freeze the point in time to be used for signing. + // It will be passed into the signer, and set on the signed HTTPRequest as the signedAt property. + let signedAt = Date() + + var signingConfig = try constructSigningConfig( + identity: identity, + signingProperties: signingProperties, + signedAt: signedAt + ) // Used to fix signingConfig.date for testing signRequest(). if let date = signingProperties.get(key: AttributeKey(name: "SigV4AuthSchemeTests")) { @@ -75,7 +83,11 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { config: crtSigningConfig ) - let sdkSignedRequest = requestBuilder.update(from: crtSignedRequest, originalRequest: unsignedRequest) + let sdkSignedRequest = requestBuilder.update( + from: crtSignedRequest, + originalRequest: unsignedRequest, + signedAt: signedAt + ) if crtSigningConfig.useAwsChunkedEncoding { guard let requestSignature = crtSignedRequest.signature else { @@ -102,7 +114,8 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { private func constructSigningConfig( identity: AWSCredentialIdentity, - signingProperties: Smithy.Attributes + signingProperties: Smithy.Attributes, + signedAt: Date ) throws -> AWSSigningConfig { guard let unsignedBody = signingProperties.get(key: SigningPropertyKeys.unsignedBody) else { throw Smithy.ClientError.authError( @@ -125,9 +138,9 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { ) } - let expiration: TimeInterval = signingProperties.get(key: SigningPropertyKeys.expiration) ?? 0 - let signedBodyHeader: AWSSignedBodyHeader = - signingProperties.get(key: SigningPropertyKeys.signedBodyHeader) ?? .none + let clockSkew = signingProperties.get(key: SigningPropertyKeys.clockSkew) ?? 0.0 + let expiration = signingProperties.get(key: SigningPropertyKeys.expiration) ?? 0.0 + let signedBodyHeader = signingProperties.get(key: SigningPropertyKeys.signedBodyHeader) ?? .none // Determine signed body value let checksumIsPresent = signingProperties.get(key: SigningPropertyKeys.checksum) != nil @@ -156,7 +169,7 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { signedBodyHeader: signedBodyHeader, signedBodyValue: signedBodyValue, flags: flags, - date: Date(), + date: signedAt.addingTimeInterval(clockSkew), service: signingName, region: signingRegion, signatureType: signatureType, @@ -196,7 +209,11 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { signatureType: .requestQueryParams, signingAlgorithm: signingAlgorithm ) - let builtRequest = await sigV4SignedRequest(requestBuilder: requestBuilder, signingConfig: signingConfig) + let builtRequest = await sigV4SignedRequest( + requestBuilder: requestBuilder, + signingConfig: signingConfig, + signedAt: date + ) guard let presignedURL = builtRequest?.destination.url else { logger.error("Failed to generate presigend url") return nil @@ -210,7 +227,8 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { public func sigV4SignedRequest( requestBuilder: SmithyHTTPAPI.HTTPRequestBuilder, - signingConfig: AWSSigningConfig + signingConfig: AWSSigningConfig, + signedAt: Date = Date() ) async -> SmithyHTTPAPI.HTTPRequest? { let originalRequest = requestBuilder.build() do { @@ -220,7 +238,11 @@ public final class AWSSigV4Signer: SmithyHTTPAuthAPI.Signer, Sendable { request: crtUnsignedRequest, config: signingConfig.toCRTType() ) - let sdkSignedRequest = requestBuilder.update(from: crtSignedRequest, originalRequest: originalRequest) + let sdkSignedRequest = requestBuilder.update( + from: crtSignedRequest, + originalRequest: originalRequest, + signedAt: signedAt + ) return sdkSignedRequest.build() } catch CommonRunTimeError.crtError(let crtError) { logger.error("Failed to sign request (CRT): \(crtError)") diff --git a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSCognitoIdentity/Sources/InternalAWSCognitoIdentity/CognitoIdentityClient.swift b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSCognitoIdentity/Sources/InternalAWSCognitoIdentity/CognitoIdentityClient.swift index fae8328bf61..1ed20ec36b9 100644 --- a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSCognitoIdentity/Sources/InternalAWSCognitoIdentity/CognitoIdentityClient.swift +++ b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSCognitoIdentity/Sources/InternalAWSCognitoIdentity/CognitoIdentityClient.swift @@ -21,6 +21,7 @@ import class Smithy.ContextBuilder import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse @_spi(SmithyReadWrite) import class SmithyJSON.Writer +import enum AWSClientRuntime.AWSClockSkewProvider import enum AWSClientRuntime.AWSRetryErrorInfoProvider import enum AWSClientRuntime.AWSRetryMode import enum AWSSDKChecksums.AWSChecksumCalculationMode @@ -411,6 +412,7 @@ extension CognitoIdentityClient { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(GetCredentialsForIdentityOutput.httpOutput(from:), GetCredentialsForIdentityOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) @@ -484,6 +486,7 @@ extension CognitoIdentityClient { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(GetIdOutput.httpOutput(from:), GetIdOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) diff --git a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSO/Sources/InternalAWSSSO/SSOClient.swift b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSO/Sources/InternalAWSSSO/SSOClient.swift index e7748dfe5a8..8b776cc83fa 100644 --- a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSO/Sources/InternalAWSSSO/SSOClient.swift +++ b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSO/Sources/InternalAWSSSO/SSOClient.swift @@ -20,6 +20,7 @@ import class Smithy.Context import class Smithy.ContextBuilder import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse +import enum AWSClientRuntime.AWSClockSkewProvider import enum AWSClientRuntime.AWSRetryErrorInfoProvider import enum AWSClientRuntime.AWSRetryMode import enum AWSSDKChecksums.AWSChecksumCalculationMode @@ -404,6 +405,7 @@ extension SSOClient { builder.serialize(ClientRuntime.QueryItemMiddleware(GetRoleCredentialsInput.queryItemProvider(_:))) builder.deserialize(ClientRuntime.DeserializeMiddleware(GetRoleCredentialsOutput.httpOutput(from:), GetRoleCredentialsOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) diff --git a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSOOIDC/Sources/InternalAWSSSOOIDC/SSOOIDCClient.swift b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSOOIDC/Sources/InternalAWSSSOOIDC/SSOOIDCClient.swift index 32c5e7a1531..766db69fac7 100644 --- a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSOOIDC/Sources/InternalAWSSSOOIDC/SSOOIDCClient.swift +++ b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSSOOIDC/Sources/InternalAWSSSOOIDC/SSOOIDCClient.swift @@ -21,6 +21,7 @@ import class Smithy.ContextBuilder import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse @_spi(SmithyReadWrite) import class SmithyJSON.Writer +import enum AWSClientRuntime.AWSClockSkewProvider import enum AWSClientRuntime.AWSRetryErrorInfoProvider import enum AWSClientRuntime.AWSRetryMode import enum AWSSDKChecksums.AWSChecksumCalculationMode @@ -415,6 +416,7 @@ extension SSOOIDCClient { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(CreateTokenOutput.httpOutput(from:), CreateTokenOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) diff --git a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSTS/Sources/InternalAWSSTS/STSClient.swift b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSTS/Sources/InternalAWSSTS/STSClient.swift index e4f3128ec01..40fb95493f0 100644 --- a/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSTS/Sources/InternalAWSSTS/STSClient.swift +++ b/Sources/Core/AWSSDKIdentity/InternalClients/InternalAWSSTS/Sources/InternalAWSSTS/STSClient.swift @@ -21,6 +21,7 @@ import class Smithy.ContextBuilder @_spi(SmithyReadWrite) import class SmithyFormURL.Writer import class SmithyHTTPAPI.HTTPRequest import class SmithyHTTPAPI.HTTPResponse +import enum AWSClientRuntime.AWSClockSkewProvider import enum AWSClientRuntime.AWSRetryErrorInfoProvider import enum AWSClientRuntime.AWSRetryMode import enum AWSSDKChecksums.AWSChecksumCalculationMode @@ -424,6 +425,7 @@ extension STSClient { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(AssumeRoleOutput.httpOutput(from:), AssumeRoleOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) @@ -499,6 +501,7 @@ extension STSClient { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(AssumeRoleWithWebIdentityOutput.httpOutput(from:), AssumeRoleWithWebIdentityOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) diff --git a/Sources/Core/SDKForSwift/Documentation.docc/SDKForSwift.md b/Sources/Core/SDKForSwift/Documentation.docc/SDKForSwift.md index 12c55286c99..16ee0a1eae7 100644 --- a/Sources/Core/SDKForSwift/Documentation.docc/SDKForSwift.md +++ b/Sources/Core/SDKForSwift/Documentation.docc/SDKForSwift.md @@ -763,8 +763,6 @@ This SDK is open-source. Code is available on Github [here](https://github.com/ [AWSSFN](../../../../../sdk-for-swift/latest/api/awssfn) -[AWSSMS](../../../../../sdk-for-swift/latest/api/awssms) - [AWSSNS](../../../../../sdk-for-swift/latest/api/awssns) [AWSSQS](../../../../../sdk-for-swift/latest/api/awssqs) diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPBindingProtocolGenerator.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPBindingProtocolGenerator.kt index c7b6da6b102..fb060ac8e55 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPBindingProtocolGenerator.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/AWSHTTPBindingProtocolGenerator.kt @@ -32,6 +32,9 @@ abstract class AWSHTTPBindingProtocolGenerator( ) : HTTPBindingProtocolGenerator(customizations) { override var serviceErrorProtocolSymbol: Symbol = AWSClientRuntimeTypes.Core.AWSServiceError + override val clockSkewProviderSymbol: Symbol + get() = AWSClientRuntimeTypes.Core.AWSClockSkewProvider + override val retryErrorInfoProviderSymbol: Symbol get() = AWSClientRuntimeTypes.Core.AWSRetryErrorInfoProvider diff --git a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSClientRuntimeTypes.kt b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSClientRuntimeTypes.kt index f0348294b7d..dee04b72ac1 100644 --- a/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSClientRuntimeTypes.kt +++ b/codegen/smithy-aws-swift-codegen/src/main/kotlin/software/amazon/smithy/aws/swift/codegen/swiftmodules/AWSClientRuntimeTypes.kt @@ -47,6 +47,7 @@ object AWSClientRuntimeTypes { runtimeSymbol("UnknownAWSHTTPServiceError", SwiftDeclaration.STRUCT, listOf("UnknownAWSHTTPServiceError")) val AWSServiceError = runtimeSymbol("AWSServiceError", SwiftDeclaration.PROTOCOL) val Sha256TreeHashMiddleware = runtimeSymbol("Sha256TreeHashMiddleware", SwiftDeclaration.STRUCT) + val AWSClockSkewProvider = runtimeSymbol("AWSClockSkewProvider", SwiftDeclaration.ENUM) val AWSRetryErrorInfoProvider = runtimeSymbol("AWSRetryErrorInfoProvider", SwiftDeclaration.ENUM) val AWSRetryMode = runtimeSymbol("AWSRetryMode", SwiftDeclaration.ENUM) val AWSPartitionDefinition = runtimeSymbol("awsPartitionJSON", SwiftDeclaration.LET) diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt index 7cc539051dd..95b35310562 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/PresignerGeneratorTests.kt @@ -46,6 +46,7 @@ extension GetFooInput { builder.interceptors.add(ClientRuntime.URLHostMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(GetFooOutput.httpOutput(from:), GetFooOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) @@ -116,6 +117,7 @@ extension PostFooInput { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(PostFooOutput.httpOutput(from:), PostFooOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) @@ -186,6 +188,7 @@ extension PutFooInput { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(PutFooOutput.httpOutput(from:), PutFooOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) @@ -258,6 +261,7 @@ extension PutObjectInput { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(PutObjectOutput.httpOutput(from:), PutObjectOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) diff --git a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt index 51859b29255..05b6ff58914 100644 --- a/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt +++ b/codegen/smithy-aws-swift-codegen/src/test/kotlin/software/amazon/smithy/aws/swift/codegen/awsquery/AWSQueryOperationStackTest.kt @@ -33,13 +33,14 @@ class AWSQueryOperationStackTest { builder.interceptors.add(ClientRuntime.ContentLengthMiddleware()) builder.deserialize(ClientRuntime.DeserializeMiddleware(NoInputAndOutputOutput.httpOutput(from:), NoInputAndOutputOutputError.httpError(from:))) builder.interceptors.add(ClientRuntime.LoggerMiddleware(clientLogMode: config.clientLogMode)) + builder.clockSkewProvider(AWSClientRuntime.AWSClockSkewProvider.provider()) builder.retryStrategy(SmithyRetries.DefaultRetryStrategy(options: config.retryStrategyOptions)) builder.retryErrorInfoProvider(AWSClientRuntime.AWSRetryErrorInfoProvider.errorInfo(for:)) builder.applySigner(ClientRuntime.SignerMiddleware()) let endpointParamsBlock = { [config] (context: Smithy.Context) in EndpointParams() } - builder.applyEndpoint(AWSClientRuntime.AWSEndpointResolverMiddleware(paramsBlock: endpointParamsBlock, resolverBlock: { [config] in try config.endpointResolver.resolve(params: ${'$'}0) })) + builder.applyEndpoint(AWSClientRuntime.AWSEndpointResolverMiddleware(paramsBlock: endpointParamsBlock, resolverBlock: { [config] in try config.endpointResolver.resolve(params: $0) })) builder.serialize(ClientRuntime.BodyMiddleware(rootNodeInfo: "", inputWritingClosure: NoInputAndOutputInput.write(value:to:))) builder.interceptors.add(ClientRuntime.ContentTypeMiddleware(contentType: "application/x-www-form-urlencoded")) builder.selectAuthScheme(ClientRuntime.AuthSchemeMiddleware())