Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changelog/feature-clock-skew-correction.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "feature",
"category": "aws-cpp-sdk-core",
"contributor": "kaiion",
"description": "Add clock skew correction: the SDK adjusts request signing timestamps by the observed client-to-service skew and retries signature errors caused by skew, so requests keep working when the client clock is off. Disable with AWS_DISABLE_CLOCK_SKEW_CORRECTION."
}
8 changes: 7 additions & 1 deletion src/aws-cpp-sdk-core/include/aws/core/client/AWSClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ namespace Aws

class AmazonWebServiceRequest;

namespace Internal
{
class ClientSkew;
}

namespace Client
{
template<typename ERROR_TYPE>
Expand Down Expand Up @@ -345,7 +350,7 @@ namespace Aws
* Try to adjust signer's clock
* return true if signer's clock is adjusted, false otherwise.
*/
bool AdjustClockSkew(HttpResponseOutcome& outcome, const char* signerName) const;
bool AdjustClockSkew(HttpResponseOutcome& outcome, const Aws::Utils::DateTime& timeRequestSent, const Aws::Utils::DateTime& timeResponseReceived, std::chrono::milliseconds attemptSkew) const;
void AddHeadersToRequest(const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest, const Http::HeaderValueCollection& headerValues) const;
void AddContentBodyToRequest(const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest, const std::shared_ptr<Aws::IOStream>& body,
bool needsContentMd5 = false, bool isChunked = false) const;
Expand All @@ -359,6 +364,7 @@ namespace Aws
std::shared_ptr<Aws::Utils::Crypto::Hash> m_hash;
long m_requestTimeoutMs;
bool m_enableClockSkewAdjustment;
mutable std::shared_ptr<Aws::Internal::ClientSkew> m_clientSkew;
Aws::String m_serviceName = "AWSBaseClient";
Aws::Client::RequestCompressionConfig m_requestCompressionConfig;
std::shared_ptr<smithy::client::UserAgentInterceptor> m_userAgentInterceptor;
Expand Down
13 changes: 13 additions & 0 deletions src/aws-cpp-sdk-core/include/aws/core/http/HttpRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include <aws/core/utils/memory/stl/AWSStreamFwd.h>
#include <aws/core/utils/stream/ResponseStream.h>
#include <aws/core/utils/UUID.h>
#include <aws/core/utils/DateTime.h>
#include <aws/crt/Optional.h>
#include <aws/core/monitoring/HttpClientMetrics.h>
#include <memory>
#include <functional>
Expand Down Expand Up @@ -558,6 +560,16 @@ namespace Aws
*/
inline void SetSigningRegion(const Aws::String& region) { m_signingRegion = region; }

/**
* Gets the per-attempt signing timestamp override set for clock-skew correction, if any.
*/
inline const Aws::Crt::Optional<Aws::Utils::DateTime>& GetSigningTimestampOverride() const { return m_signingTimestampOverride; }
/**
* Sets an explicit signing timestamp for this attempt (now() + AttemptSkew). The signer uses it
* instead of its own clock; set per attempt so concurrent operations don't share skew state.
*/
inline void SetSigningTimestampOverride(const Aws::Utils::DateTime& signingTime) { m_signingTimestampOverride = signingTime; }

/**
* Add a request metric
* @param key, HttpClientMetricsKey defined in HttpClientMetrics.cpp
Expand Down Expand Up @@ -618,6 +630,7 @@ namespace Aws
DataSentEventHandler m_onDataSent;
ContinueRequestHandler m_continueRequest;
Aws::String m_signingRegion;
Aws::Crt::Optional<Aws::Utils::DateTime> m_signingTimestampOverride;
Aws::String m_signingAccessKey;
Aws::String m_resolvedRemoteHost;
Aws::Monitoring::HttpClientMetricsCollection m_httpRequestMetrics;
Expand Down
5 changes: 0 additions & 5 deletions src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,6 @@ namespace client
return AwsClientRequestSigning<AuthSchemesVariantT>::SignEventMessage(message, seed, ctx, m_authSchemes);
}

bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption) const override
{
return AwsClientRequestSigning<AuthSchemesVariantT>::AdjustClockSkew(outcome, authSchemeOption, m_authSchemes);
}

IdentityOutcome ResolveIdentity(const AwsSmithyClientAsyncRequestContext& ctx) const override {
return AwsClientRequestSigning<AuthSchemesVariantT>::ResolveIdentity(ctx, m_authSchemes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ namespace smithy

Aws::Crt::Optional<AwsCoreError> m_lastError;

std::chrono::milliseconds m_attemptSkew{0};
Aws::Utils::DateTime m_timeRequestSent;
Aws::Utils::DateTime m_timeResponseReceived;

size_t m_retryCount;
Aws::Vector<void*> m_monitoringContexts;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ namespace Aws
}

class AmazonWebServiceRequest;

namespace Internal
{
class ClientSkew;
}
}

namespace Aws
Expand Down Expand Up @@ -222,7 +227,8 @@ namespace client
virtual ResolveEndpointOutcome ResolveEndpoint(const Aws::Endpoint::EndpointParameters& endpointParameters, EndpointUpdateCallback&& epCallback) const = 0;
virtual SelectAuthSchemeOptionOutcome SelectAuthSchemeOption(const AwsSmithyClientAsyncRequestContext& ctx) const = 0;
virtual SigningOutcome SignHttpRequest(std::shared_ptr<HttpRequest> httpRequest, const AwsSmithyClientAsyncRequestContext& ctx) const = 0;
virtual bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption) const = 0;
bool AdjustClockSkew(HttpResponseOutcome& outcome, const AwsSmithyClientAsyncRequestContext& ctx) const;
void RecordClockSkew(const Aws::Http::HttpResponse& response, const AwsSmithyClientAsyncRequestContext& ctx) const;
virtual IdentityOutcome ResolveIdentity(const AwsSmithyClientAsyncRequestContext& ctx) const = 0;
virtual GetContextEndpointParametersOutcome GetContextEndpointParameters(const AwsSmithyClientAsyncRequestContext& ctx) const = 0;
AwsSmithyClientBase::ResolveEndpointOutcome ResolveEndpointFromRequest(
Expand All @@ -241,6 +247,7 @@ namespace client
std::shared_ptr<Aws::Client::AWSErrorMarshaller> m_errorMarshaller;
Aws::Vector<std::shared_ptr<smithy::interceptor::Interceptor>> m_interceptors{};
std::shared_ptr<smithy::client::UserAgentInterceptor> m_userAgentInterceptor;
mutable std::shared_ptr<Aws::Internal::ClientSkew> m_clientSkew;
private:
void UpdateAuthSchemeFromEndpoint(const Aws::Endpoint::AWSEndpoint& endpoint, AuthSchemeOption& authscheme) const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@
namespace smithy
{
static const char AWS_SMITHY_CLIENT_SIGNING_TAG[] = "AwsClientRequestSigning";
//4 Minutes
static const std::chrono::milliseconds TIME_DIFF_MAX = std::chrono::minutes(4);
//-4 Minutes
static const std::chrono::milliseconds TIME_DIFF_MIN = std::chrono::minutes(-4);

template <typename AuthSchemesVariantT>
class AwsClientRequestSigning
Expand Down Expand Up @@ -142,28 +138,6 @@ namespace smithy
return {authScheme};
}

static bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
{
assert(!outcome.IsSuccess());
AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_SIGNING_TAG, "If the signature check failed. This could be because of a time skew. Attempting to adjust the signer.");

using DateTime = Aws::Utils::DateTime;
DateTime serverTime = smithy::client::Utils::GetServerTimeFromError(outcome.GetError());

auto authSchemeOutcome = ResolveAuthScheme(authSchemeOption, authSchemes);
if (!authSchemeOutcome.IsSuccess())
{
return false;
}

ClockSkewVisitor visitor(outcome, serverTime, authSchemeOption);
AuthSchemesVariantT authScheme = authSchemeOutcome.GetResult().value();
authScheme.Visit(visitor);

return visitor.m_resultShouldWait;
}


protected:
struct IdentityVisitor
Expand Down Expand Up @@ -360,61 +334,5 @@ namespace smithy
return std::move(*visitor.result);
}

struct ClockSkewVisitor
{
using DateTime = Aws::Utils::DateTime;
using DateFormat = Aws::Utils::DateFormat;
using ClientError = Aws::Client::AWSError<Aws::Client::CoreErrors>;

ClockSkewVisitor(HttpResponseOutcome& outcome, const DateTime& serverTime, const AuthSchemeOption& targetAuthSchemeOption)
: m_outcome(outcome), m_serverTime(serverTime), m_targetAuthSchemeOption(targetAuthSchemeOption)
{
}

bool m_resultShouldWait = false;
HttpResponseOutcome& m_outcome;
const Aws::Utils::DateTime& m_serverTime;
const AuthSchemeOption& m_targetAuthSchemeOption;

template <typename AuthSchemeAlternativeT>
void operator()(AuthSchemeAlternativeT& authScheme)
{
// Auth Scheme Variant alternative contains the requested auth option
assert(strcmp(authScheme.schemeId, m_targetAuthSchemeOption.schemeId) == 0);

using IdentityT = typename std::remove_reference<decltype(authScheme)>::type::IdentityT;
using Signer = AwsSignerBase<IdentityT>;

std::shared_ptr<Signer> signer = authScheme.signer();
if (!signer)
{
AWS_LOGSTREAM_ERROR(AWS_SMITHY_CLIENT_SIGNING_TAG, "Failed to adjust signing clock skew. Signer is null.");
return;
}

const auto signingTimestamp = signer->GetSigningTimestamp();
if (!m_serverTime.WasParseSuccessful() || m_serverTime == DateTime())
{
AWS_LOGSTREAM_DEBUG(AWS_SMITHY_CLIENT_SIGNING_TAG, "Date header was not found in the response, can't attempt to detect clock skew");
return;
}

AWS_LOGSTREAM_DEBUG(AWS_SMITHY_CLIENT_SIGNING_TAG, "Server time is " << m_serverTime.ToGmtString(DateFormat::RFC822) << ", while client time is " << DateTime::Now().ToGmtString(DateFormat::RFC822));
auto diff = DateTime::Diff(m_serverTime, signingTimestamp);
//only try again if clock skew was the cause of the error.
if (diff >= TIME_DIFF_MAX || diff <= TIME_DIFF_MIN)
{
diff = DateTime::Diff(m_serverTime, DateTime::Now());
AWS_LOGSTREAM_INFO(AWS_SMITHY_CLIENT_SIGNING_TAG, "Computed time difference as " << diff.count() << " milliseconds. Adjusting signer with the skew.");
signer->SetClockSkew(diff);
ClientError newError(m_outcome.GetError());
newError.SetRetryableType(Aws::Client::RetryableType::RETRYABLE);

m_outcome = std::move(newError);
m_resultShouldWait = true;
}
}
};

};
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ namespace smithy {
awsSigningConfig.SetSignatureType(signatureType);
awsSigningConfig.SetRegion(serviceName.c_str());
awsSigningConfig.SetService(region.c_str());
awsSigningConfig.SetSigningTimepoint(GetSigningTimestamp().UnderlyingTimestamp());
const Aws::Utils::DateTime sigV4aSigningTime = request.GetSigningTimestampOverride() ? request.GetSigningTimestampOverride().value() : GetSigningTimestamp();
awsSigningConfig.SetSigningTimepoint(sigV4aSigningTime.UnderlyingTimestamp());
awsSigningConfig.SetUseDoubleUriEncode(m_urlEscape);
awsSigningConfig.SetShouldNormalizeUriPath(true);
awsSigningConfig.SetOmitSessionToken(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ bool AWSAuthEventStreamV4Signer::SignRequestWithCreds(Http::HttpRequest& request
request.SetHeaderValue(Aws::Auth::AWSAuthHelper::X_AMZ_CONTENT_SHA256, EVENT_STREAM_CONTENT_SHA256);

//calculate date header to use in internal signature (this also goes into date header).
DateTime now = GetSigningTimestamp();
DateTime now = request.GetSigningTimestampOverride() ? request.GetSigningTimestampOverride().value() : GetSigningTimestamp();
Aws::String dateHeaderValue = now.ToGmtString(DateFormat::ISO_8601_BASIC);
request.SetHeaderValue(AWS_DATE_HEADER, dateHeaderValue);

Expand Down
5 changes: 3 additions & 2 deletions src/aws-cpp-sdk-core/source/auth/signer/AWSAuthV4Signer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ bool AWSAuthV4Signer::SignRequestWithSigV4a(Aws::Http::HttpRequest& request, con
awsSigningConfig.SetSignatureType(signatureType);
awsSigningConfig.SetRegion(region);
awsSigningConfig.SetService(serviceName);
awsSigningConfig.SetSigningTimepoint(GetSigningTimestamp().UnderlyingTimestamp());
const DateTime sigV4aSigningTime = request.GetSigningTimestampOverride() ? request.GetSigningTimestampOverride().value() : GetSigningTimestamp();
awsSigningConfig.SetSigningTimepoint(sigV4aSigningTime.UnderlyingTimestamp());
awsSigningConfig.SetUseDoubleUriEncode(m_urlEscapePath);
awsSigningConfig.SetShouldNormalizeUriPath(true);
awsSigningConfig.SetOmitSessionToken(false);
Expand Down Expand Up @@ -254,7 +255,7 @@ bool AWSAuthV4Signer::SignRequestWithCreds(Aws::Http::HttpRequest& request, cons
}

//calculate date header to use in internal signature (this also goes into date header).
DateTime now = GetSigningTimestamp();
DateTime now = request.GetSigningTimestampOverride() ? request.GetSigningTimestampOverride().value() : GetSigningTimestamp();
Aws::String dateHeaderValue = now.ToGmtString(DateFormat::ISO_8601_BASIC);
request.SetHeaderValue(AWS_DATE_HEADER, dateHeaderValue);

Expand Down
Loading
Loading