GameLiftPlugin/Source/AWSSDK/Include/smithy/client/common/AwsSmithyRequestSigning.h (167 lines of code) (raw):
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/client/common/AwsSmithyClientUtils.h>
#include <smithy/identity/auth/AuthSchemeOption.h>
#include <smithy/identity/identity/AwsIdentity.h>
#include <smithy/identity/resolver/AwsIdentityResolverBase.h>
#include <smithy/identity/signer/AwsSignerBase.h>
#include <aws/core/utils/FutureOutcome.h>
#include <aws/core/client/AWSError.h>
#include <aws/core/http/HttpRequest.h>
#include <aws/crt/Variant.h>
#include <aws/crt/Optional.h>
#include <aws/core/utils/memory/stl/AWSMap.h>
#include <cassert>
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
{
public:
using HttpRequest = Aws::Http::HttpRequest;
using SigningError = Aws::Client::AWSError<Aws::Client::CoreErrors>;
using SigningOutcome = Aws::Utils::FutureOutcome<std::shared_ptr<HttpRequest>, SigningError>;
using HttpResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
static SigningOutcome SignRequest(std::shared_ptr<HttpRequest> HTTPRequest, const AuthSchemeOption& authSchemeOption,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
{
auto authSchemeIt = authSchemes.find(authSchemeOption.schemeId);
if (authSchemeIt == authSchemes.end())
{
assert(!"Auth scheme has not been found for a given auth option!");
return (SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Requested AuthSchemeOption was not found within client Auth Schemes",
false/*retryable*/));
}
const AuthSchemesVariantT& authScheme = authSchemeIt->second;
return SignWithAuthScheme(std::move(HTTPRequest), authScheme, authSchemeOption);
}
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 authSchemeIt = authSchemes.find(authSchemeOption.schemeId);
if (authSchemeIt == authSchemes.end())
{
assert(!"Auth scheme has not been found for a given auth option!");
return false;
}
AuthSchemesVariantT authScheme = authSchemeIt->second;
ClockSkewVisitor visitor(outcome, serverTime, authSchemeOption);
authScheme.Visit(visitor);
return visitor.m_resultShouldWait;
}
protected:
struct SignerVisitor
{
SignerVisitor(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemeOption& targetAuthSchemeOption)
: m_httpRequest(std::move(httpRequest)), m_targetAuthSchemeOption(targetAuthSchemeOption)
{
}
const std::shared_ptr<HttpRequest> m_httpRequest;
const AuthSchemeOption& m_targetAuthSchemeOption;
Aws::Crt::Optional<SigningOutcome> result;
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 IdentityResolver = IdentityResolverBase<IdentityT>;
using Signer = AwsSignerBase<IdentityT>;
std::shared_ptr<IdentityResolver> identityResolver = authScheme.identityResolver();
if (!identityResolver)
{
result.emplace(SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Auth scheme provided a nullptr identityResolver",
false/*retryable*/));
return;
}
auto identityResult = identityResolver->getIdentity(m_targetAuthSchemeOption.identityProperties(), m_targetAuthSchemeOption.identityProperties());
if (!identityResult.IsSuccess())
{
result.emplace(identityResult.GetError());
return;
}
auto identity = std::move(identityResult.GetResultWithOwnership());
std::shared_ptr<Signer> signer = authScheme.signer();
if (!signer)
{
result.emplace(SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Auth scheme provided a nullptr signer",
false/*retryable*/));
return;
}
result.emplace(signer->sign(m_httpRequest, *identity, m_targetAuthSchemeOption.signerProperties()));
}
};
static
SigningOutcome SignWithAuthScheme(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemesVariantT& authSchemesVariant,
const AuthSchemeOption& targetAuthSchemeOption)
{
SignerVisitor visitor(httpRequest, targetAuthSchemeOption);
AuthSchemesVariantT authSchemesVariantCopy(authSchemesVariant); // TODO: allow const visiting
authSchemesVariantCopy.Visit(visitor);
if (!visitor.result)
{
return (SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Failed to sign with an unknown error",
false/*retryable*/));
}
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;
}
}
};
};
}