-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement clock skew interceptor #972
Merged
Merged
Changes from 52 commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
5b7e337
Add `until` convenience function
lauzadis 023c845
ClockSkew integration / interceptor
lauzadis 371e993
Add Skew RetryErrorType
lauzadis e2bbde5
Use skew from execution context
lauzadis 5a42d82
Final
lauzadis 10c8013
ktlint
lauzadis b21ae43
ktlint
lauzadis dd94acc
ktlint
lauzadis e83af28
Add tests
lauzadis 3c8ca1a
Store clock skew and apply to all requests
lauzadis 6234df5
Duration.ZERO
lauzadis ee15721
ktlintFormat
lauzadis be94c7c
Don't need Duration/nanoseconds
lauzadis f2cfa77
KDocs
lauzadis a22de25
Add additional assertions
lauzadis 4c50705
Actually update the interceptor state
lauzadis ad55bd8
Changelog
lauzadis cfc0ce5
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-clo…
lauzadis 5cfcb17
Remove usage of ByteArrayContent
lauzadis c37e2e8
Add `until` tests
lauzadis d3520c8
Make getSkew internal
lauzadis bb57855
Refactor clock skew application
lauzadis fd11afd
Remove `Skew` error type
lauzadis 1230226
Use `Instant.now()` for client time, lower some logs to debug level, …
lauzadis 2849f66
use atomic
lauzadis bb2bce8
apiDump
lauzadis c4aa35b
Use atomics
lauzadis 0007895
apiDump
lauzadis 52c5d3b
Refactor to extension function
lauzadis 3fd3f9f
Proper skew logic
lauzadis f44477d
Unused import
lauzadis b822870
ktlint
lauzadis 7c9c012
Remove unnecessary `Duration.ZERO -`
lauzadis 6c01e69
Honest KDocs
lauzadis 672740e
`var` -> `val`
lauzadis b139e49
Remove `getSkew` in favor of `Instant.until`
lauzadis cf58ca7
Use `Instant.until`
lauzadis 839ef98
Remove import too
lauzadis ae609bf
Changelog
lauzadis 3b5d2e1
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-clo…
lauzadis 3924710
Use client's approximate signing time instead of `Instant.now()`
lauzadis 928d58f
Add import
lauzadis 34c1b3b
Use service exceptions to determine skew
lauzadis 2a85575
ktlint
lauzadis 343ae26
Fix exceptions handling
lauzadis e590d52
Relocate to aws-protocol-core
lauzadis 5ffebe9
Relocate to aws-protocol-core
lauzadis 19a9ea9
Refactor to section writer
lauzadis 6c4e3bf
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-clo…
lauzadis badf986
ktlint
lauzadis 7becf58
correct possible skew error codes
lauzadis 8d52429
correct code in test
lauzadis 7582dd0
Rename `responseCodeDescription` to `errorCode` and make it required
lauzadis 66fa578
Merge branch 'main' of github.com:awslabs/smithy-kotlin into feat-clo…
lauzadis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"id": "6de10487-c3a0-4c63-929a-ba11a415ea8f", | ||
"type": "feature", | ||
"description": "Detect and automatically correct clock skew to prevent signing errors" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
...ws-protocol-core/common/src/aws/smithy/kotlin/runtime/awsprotocol/ClockSkewInterceptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
package aws.smithy.kotlin.runtime.awsprotocol | ||
|
||
import aws.smithy.kotlin.runtime.ErrorMetadata | ||
import aws.smithy.kotlin.runtime.SdkBaseException | ||
import aws.smithy.kotlin.runtime.ServiceErrorMetadata | ||
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext | ||
import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext | ||
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor | ||
import aws.smithy.kotlin.runtime.http.operation.HttpOperationContext | ||
import aws.smithy.kotlin.runtime.http.request.HttpRequest | ||
import aws.smithy.kotlin.runtime.http.response.HttpResponse | ||
import aws.smithy.kotlin.runtime.http.response.header | ||
import aws.smithy.kotlin.runtime.telemetry.logging.logger | ||
import aws.smithy.kotlin.runtime.time.Instant | ||
import aws.smithy.kotlin.runtime.time.until | ||
import aws.smithy.kotlin.runtime.util.get | ||
import kotlinx.atomicfu.* | ||
import kotlin.coroutines.coroutineContext | ||
import kotlin.time.Duration | ||
import kotlin.time.Duration.Companion.minutes | ||
|
||
/** | ||
* An interceptor used to detect clock skew (difference between client and server clocks) and apply a correction. | ||
*/ | ||
public class ClockSkewInterceptor : HttpInterceptor { | ||
public companion object { | ||
/** | ||
* How much must the clock be skewed before attempting correction | ||
*/ | ||
public val CLOCK_SKEW_THRESHOLD: Duration = 4.minutes | ||
|
||
/** | ||
* Determine whether the client's clock is skewed relative to the server. | ||
* @return true if the service's response represents a definite clock skew error | ||
* OR a *possible* clock skew error AND the skew exists. false otherwise. | ||
* @param responseCodeDescription the server's response code description | ||
* @param serverTime the server's time | ||
*/ | ||
internal fun Instant.isSkewed(serverTime: Instant, responseCodeDescription: String?): Boolean = | ||
responseCodeDescription?.let { | ||
CLOCK_SKEW_ERROR_CODES.contains(it) || (POSSIBLE_CLOCK_SKEW_ERROR_CODES.contains(it) && until(serverTime).absoluteValue >= CLOCK_SKEW_THRESHOLD) | ||
} ?: false | ||
|
||
// Errors definitely caused by clock skew | ||
private val CLOCK_SKEW_ERROR_CODES = listOf( | ||
"RequestTimeTooSkewed", | ||
"RequestExpired", | ||
"RequestInTheFuture", | ||
) | ||
|
||
// Errors possibly caused by clock skew | ||
private val POSSIBLE_CLOCK_SKEW_ERROR_CODES = listOf( | ||
"InvalidSignatureException", | ||
"SignatureDoesNotMatch", | ||
"AuthFailure", | ||
) | ||
} | ||
|
||
// Clock skew to be applied to all requests | ||
private val _currentSkew: AtomicRef<Duration?> = atomic(null) | ||
|
||
/** | ||
* Apply the previously-computed skew, if it's set, to the execution context before signing | ||
*/ | ||
public override suspend fun modifyBeforeSigning(context: ProtocolRequestInterceptorContext<Any, HttpRequest>): HttpRequest { | ||
val logger = coroutineContext.logger<ClockSkewInterceptor>() | ||
|
||
val skew = _currentSkew.value | ||
skew?.let { | ||
logger.info { "applying clock skew $skew to client" } | ||
context.executionContext[HttpOperationContext.ClockSkew] = skew | ||
} | ||
|
||
context.executionContext[HttpOperationContext.ClockSkewApproximateSigningTime] = Instant.now() | ||
|
||
return context.protocolRequest | ||
} | ||
|
||
/** | ||
* After receiving a response, check if the client clock is skewed and apply a correction if necessary. | ||
*/ | ||
public override suspend fun modifyBeforeAttemptCompletion(context: ResponseInterceptorContext<Any, Any, HttpRequest, HttpResponse?>): Result<Any> { | ||
val logger = coroutineContext.logger<ClockSkewInterceptor>() | ||
|
||
val serverTime = context.protocolResponse?.header("Date")?.let { | ||
Instant.fromRfc5322(it) | ||
} ?: run { | ||
logger.debug { "service did not return \"Date\" header, skipping skew calculation" } | ||
return context.response | ||
} | ||
|
||
val clientTime = context.protocolRequest.headers["Date"]?.let { | ||
Instant.fromRfc5322(it) | ||
} ?: context.protocolRequest.headers["x-amz-date"]?.let { | ||
Instant.fromIso8601(it) | ||
} ?: context.executionContext[HttpOperationContext.ClockSkewApproximateSigningTime] | ||
|
||
val ex = (context.response.exceptionOrNull() as? SdkBaseException) ?: return context.response | ||
val errorCode = ex.sdkErrorMetadata.attributes.getOrNull(ServiceErrorMetadata.ErrorCode) | ||
|
||
if (clientTime.isSkewed(serverTime, errorCode)) { | ||
val skew = clientTime.until(serverTime) | ||
logger.warn { "client clock ($clientTime) is skewed $skew from the server ($serverTime), applying correction" } | ||
_currentSkew.getAndSet(skew) | ||
context.executionContext[HttpOperationContext.ClockSkew] = skew | ||
|
||
// Mark the exception as retryable | ||
ex.sdkErrorMetadata.attributes[ErrorMetadata.Retryable] = true | ||
return Result.failure(ex) | ||
} | ||
|
||
return context.response | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
responseCodeDescription
->errorCode
Also
responseCodeDescription
should probably be non-nullable since this always returns false otherwise which means it's really a required parameter for determining if a clock is skewed.