Skip to content

Commit

Permalink
wip - wire smithy client to use account id resolved from identity for…
Browse files Browse the repository at this point in the history
… endpoint paramters
  • Loading branch information
sbiscigl committed Feb 26, 2025
1 parent ee92bb3 commit f819175
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 57 deletions.
32 changes: 30 additions & 2 deletions src/aws-cpp-sdk-core/include/smithy/client/AwsSmithyClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,24 @@ namespace client
false/*retryable*/);
}

SigningOutcome SignHttpRequest(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemeOption& targetAuthSchemeOption) const override
SigningOutcome SignHttpRequest(std::shared_ptr<HttpRequest> httpRequest, const AwsSmithyClientAsyncRequestContext& ctx) const override
{
return AwsClientRequestSigning<AuthSchemesVariantT>::SignRequest(httpRequest, targetAuthSchemeOption, m_authSchemes);
return AwsClientRequestSigning<AuthSchemesVariantT>::SignRequest(httpRequest, 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);
}

RefreshBuiltInOutcome RefreshBuiltinParameters(const AwsSmithyClientAsyncRequestContext& ctx) const override {
return RefreshBuiltinParametersImpl(ctx);
}

ResponseT MakeRequestDeserialize(Aws::AmazonWebServiceRequest const * const request,
const char* requestName,
Aws::Http::HttpMethod method,
Expand Down Expand Up @@ -247,6 +255,26 @@ namespace client
std::shared_ptr<ServiceAuthSchemeResolverT> m_authSchemeResolver{};
Aws::UnorderedMap<Aws::String, AuthSchemesVariantT> m_authSchemes{};
std::shared_ptr<SerializerT> m_serializer{};

private:
template<typename T, typename = void>
struct HasAccountId : std::false_type {};

template<typename T>
struct HasAccountId<T, decltype(void(std::declval<T>().accountId))> : std::true_type {};

template<typename ConfigT = ServiceClientConfigurationT, typename std::enable_if<HasAccountId<ConfigT>::value, int>::type = 0>
RefreshBuiltInOutcome RefreshBuiltinParametersImpl(const AwsSmithyClientAsyncRequestContext& ctx) const {
//TODO: use identity in context to get account id
AWS_UNREFERENCED_PARAM(ctx);
return AWSError{CoreErrors::UNKNOWN, "NotImplemented", "method not implemented", false};
}

template<typename ConfigT = ServiceClientConfigurationT, typename std::enable_if<!HasAccountId<ConfigT>::value, int>::type = 0>
RefreshBuiltInOutcome RefreshBuiltinParametersImpl(const AwsSmithyClientAsyncRequestContext& ctx) const {
AWS_UNREFERENCED_PARAM(ctx);
return Aws::NoResult{};
}
};

} // namespace client
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ namespace smithy
ResponseHandlerFunc m_responseHandler;
std::shared_ptr<Aws::Utils::Threading::Executor> m_pExecutor;
std::shared_ptr<interceptor::InterceptorContext> m_interceptorContext;
std::shared_ptr<smithy::AwsIdentity> m_awsIdentity;
};
} // namespace client
} // namespace smithy
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <aws/core/utils/FutureOutcome.h>
#include <aws/core/utils/memory/stl/AWSMap.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/NoResult.h>
#include <aws/core/http/HttpClientFactory.h>
#include <aws/core/client/AWSErrorMarshaller.h>
#include <aws/core/AmazonWebServiceResult.h>
Expand Down Expand Up @@ -84,6 +85,8 @@ namespace client
using SelectAuthSchemeOptionOutcome = Aws::Utils::Outcome<AuthSchemeOption, AWSError>;
using ResolveEndpointOutcome = Aws::Utils::Outcome<Aws::Endpoint::AWSEndpoint, AWSError>;
using StreamOutcome = Aws::Utils::Outcome<Aws::AmazonWebServiceResult<Aws::Utils::Stream::ResponseStream>, AWSError >;
using IdentityOutcome = Aws::Utils::Outcome<std::shared_ptr<smithy::AwsIdentity>, AWSError>;
using RefreshBuiltInOutcome = Aws::Utils::Outcome<Aws::NoResult, AWSError>;

AwsSmithyClientBase(Aws::UniquePtr<Aws::Client::ClientConfiguration>&& clientConfig,
Aws::String serviceName,
Expand Down Expand Up @@ -188,8 +191,10 @@ 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 AuthSchemeOption& targetAuthSchemeOption) 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;
virtual IdentityOutcome ResolveIdentity(const AwsSmithyClientAsyncRequestContext& ctx) const = 0;
virtual RefreshBuiltInOutcome RefreshBuiltinParameters(const AwsSmithyClientAsyncRequestContext& ctx) const = 0;

std::shared_ptr<Aws::Client::ClientConfiguration> m_clientConfig;
Aws::String m_serviceName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,42 @@ namespace smithy
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>>;
using IdentityOutcome = Aws::Utils::Outcome<std::shared_ptr<smithy::AwsIdentity>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;

static SigningOutcome SignRequest(std::shared_ptr<HttpRequest> HTTPRequest, const AuthSchemeOption& authSchemeOption,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
static IdentityOutcome ResolveIdentity(const client::AwsSmithyClientAsyncRequestContext& ctx,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
{
auto authSchemeIt = authSchemes.find(ctx.m_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*/));
}

auto authSchemeIt = authSchemes.find(authSchemeOption.schemeId);
const AuthSchemesVariantT& authScheme = authSchemeIt->second;
IdentityVisitor visitor(ctx);
AuthSchemesVariantT authSchemesVariantCopy(authScheme); // 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);
}

static SigningOutcome SignRequest(std::shared_ptr<HttpRequest> HTTPRequest,
const client::AwsSmithyClientAsyncRequestContext& ctx,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
{
auto authSchemeIt = authSchemes.find(ctx.m_authSchemeOption.schemeId);
if (authSchemeIt == authSchemes.end())
{
assert(!"Auth scheme has not been found for a given auth option!");
Expand All @@ -55,8 +85,9 @@ namespace smithy

const AuthSchemesVariantT& authScheme = authSchemeIt->second;

return SignWithAuthScheme(std::move(HTTPRequest), authScheme, authSchemeOption);
return SignWithAuthScheme(std::move(HTTPRequest), authScheme, ctx);
}

static SigningOutcome PreSignRequest(std::shared_ptr<HttpRequest> httpRequest,
const AuthSchemeOption& authSchemeOption,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes,
Expand Down Expand Up @@ -113,59 +144,74 @@ namespace smithy


protected:
struct IdentityVisitor
{
IdentityVisitor(const client::AwsSmithyClientAsyncRequestContext& ctx): m_requestContext(ctx)
{
}

const client::AwsSmithyClientAsyncRequestContext& m_requestContext;
Aws::Crt::Optional<IdentityOutcome> result;

template <typename AuthSchemeAlternativeT>
void operator()(AuthSchemeAlternativeT& authScheme)
{
using IdentityT = typename std::remove_reference<decltype(authScheme)>::type::IdentityT;
using IdentityResolver = IdentityResolverBase<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;
}

//relay service params in additional properties which will be relevant in credential resolution
// example: bucket Name
Aws::UnorderedMap<Aws::String, Aws::Crt::Variant<Aws::String, bool>> additionalIdentityProperties;
const auto& serviceSpecificParameters = m_requestContext.m_pRequest->GetServiceSpecificParameters();
if(serviceSpecificParameters)
{
for(const auto& propPair : serviceSpecificParameters->parameterMap)
{
additionalIdentityProperties.emplace(propPair.first,Aws::Crt::Variant<Aws::String, bool>{propPair.second} );
}
}

auto identityResult = identityResolver->getIdentity(m_requestContext.m_authSchemeOption.identityProperties(), additionalIdentityProperties);
if (!identityResult.IsSuccess())
{
result.emplace(identityResult.GetError());
return;
}
result.emplace(std::move(identityResult.GetResultWithOwnership()));
}
};

struct SignerVisitor
{
SignerVisitor(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemeOption& targetAuthSchemeOption)
: m_httpRequest(std::move(httpRequest)), m_targetAuthSchemeOption(targetAuthSchemeOption)
SignerVisitor(std::shared_ptr<HttpRequest> httpRequest, const client::AwsSmithyClientAsyncRequestContext& ctx)
: m_httpRequest(std::move(httpRequest)), m_requestContext(ctx)
{
}

const std::shared_ptr<HttpRequest> m_httpRequest;
const AuthSchemeOption& m_targetAuthSchemeOption;
const client::AwsSmithyClientAsyncRequestContext& m_requestContext;

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);
assert(strcmp(authScheme.schemeId, m_requestContext.m_authSchemeOption.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;
}

//relay service params in additional properties which will be relevant in credential resolution
// example: bucket Name
Aws::UnorderedMap<Aws::String, Aws::Crt::Variant<Aws::String, bool>> additionalIdentityProperties;
const auto& serviceSpecificParameters = m_httpRequest->GetServiceSpecificParameters();
if(serviceSpecificParameters)
{
for(const auto& propPair : serviceSpecificParameters->parameterMap)
{
additionalIdentityProperties.emplace(propPair.first,Aws::Crt::Variant<Aws::String, bool>{propPair.second} );
}
}

auto identityResult = identityResolver->getIdentity(m_targetAuthSchemeOption.identityProperties(), additionalIdentityProperties);

if (!identityResult.IsSuccess())
{
result.emplace(identityResult.GetError());
return;
}
auto identity = std::move(identityResult.GetResultWithOwnership());

std::shared_ptr<Signer> signer = authScheme.signer();
if (!signer)
{
Expand All @@ -176,7 +222,9 @@ namespace smithy
return;
}

result.emplace(signer->sign(m_httpRequest, *identity, m_targetAuthSchemeOption.signerProperties()));
result.emplace(signer->sign(m_httpRequest,
*static_cast<IdentityT*>(m_requestContext.m_awsIdentity.get()),
m_requestContext.m_authSchemeOption.signerProperties()));
}
};

Expand Down Expand Up @@ -236,11 +284,11 @@ namespace smithy
}
};

static
SigningOutcome SignWithAuthScheme(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemesVariantT& authSchemesVariant,
const AuthSchemeOption& targetAuthSchemeOption)
static SigningOutcome SignWithAuthScheme(std::shared_ptr<HttpRequest> httpRequest,
const AuthSchemesVariantT& authSchemesVariant,
const client::AwsSmithyClientAsyncRequestContext& ctx)
{
SignerVisitor visitor(httpRequest, targetAuthSchemeOption);
SignerVisitor visitor(httpRequest, ctx);
AuthSchemesVariantT authSchemesVariantCopy(authSchemesVariant); // TODO: allow const visiting
authSchemesVariantCopy.Visit(visitor);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,30 @@ void AwsSmithyClientBase::MakeRequestAsync(Aws::AmazonWebServiceRequest const* c
}
pRequestCtx->m_authSchemeOption = std::move(authSchemeOptionOutcome.GetResultWithOwnership());
assert(pRequestCtx->m_authSchemeOption.schemeId);

// resolve identity
auto identityOutcome = this->ResolveIdentity(*pRequestCtx);
if (!identityOutcome.IsSuccess())
{
pExecutor->Submit([identityOutcome, responseHandler]() mutable
{
responseHandler(std::move(identityOutcome));
});
return;
}
pRequestCtx->m_awsIdentity = std::move(identityOutcome.GetResultWithOwnership());

// refresh built-in endpoint params with the request context
const auto refreshBuiltInsOutcome = this->RefreshBuiltinParameters(*pRequestCtx);
if (!refreshBuiltInsOutcome.IsSuccess())
{
pExecutor->Submit([refreshBuiltInsOutcome, responseHandler]() mutable
{
responseHandler(std::move(refreshBuiltInsOutcome.GetError()));
});
return;
}

Aws::Endpoint::EndpointParameters epParams = request ? request->GetEndpointContextParams() : Aws::Endpoint::EndpointParameters();
const auto authSchemeEpParams = pRequestCtx->m_authSchemeOption.endpointParameters();
epParams.insert(epParams.end(), authSchemeEpParams.begin(), authSchemeEpParams.end());
Expand Down Expand Up @@ -323,7 +347,7 @@ void AwsSmithyClientBase::AttemptOneRequestAsync(std::shared_ptr<AwsSmithyClient
};

SigningOutcome signingOutcome = TracingUtils::MakeCallWithTiming<SigningOutcome>([&]() -> SigningOutcome {
return this->SignHttpRequest(pRequestCtx->m_httpRequest, pRequestCtx->m_authSchemeOption);
return this->SignHttpRequest(pRequestCtx->m_httpRequest, *pRequestCtx);
},
TracingUtils::SMITHY_CLIENT_SIGNING_METRIC,
*m_clientConfig->telemetryProvider->getMeter(this->GetServiceClientName(), {}),
Expand Down
16 changes: 8 additions & 8 deletions tests/aws-cpp-sdk-core-tests/smithy/client/SmithyClientTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,9 @@ class TestClient : public MySmithyClient
return MySmithyClient::SelectAuthSchemeOption(ctx);
}

SigningOutcome SignRequest(std::shared_ptr<HttpRequest> httpRequest, const smithy::AuthSchemeOption& targetAuthSchemeOption) const
SigningOutcome SignRequest(std::shared_ptr<HttpRequest> httpRequest, const smithy::client::AwsSmithyClientAsyncRequestContext& ctx) const
{
return MySmithyClient::SignHttpRequest(httpRequest, targetAuthSchemeOption);
return MySmithyClient::SignHttpRequest(httpRequest, ctx);
}


Expand Down Expand Up @@ -239,15 +239,15 @@ TEST_F(SmithyClientTest, testSigV4) {
ctx.m_pRequest = nullptr;

auto res = ptr->SelectAuthSchemeOption(ctx);

EXPECT_EQ(res.IsSuccess(), true);
EXPECT_EQ(res.GetResult().schemeId, key);
ctx.m_authSchemeOption = res.GetResultWithOwnership();

Aws::String uri{"https://treasureisland-cb93079d-24a0-4862-8es2-88456ead.xyz.amazonaws.com"};

std::shared_ptr<Aws::Http::HttpRequest> httpRequest(Aws::Http::CreateHttpRequest(uri, Aws::Http::HttpMethod::HTTP_GET, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));

auto res2 = ptr->SignRequest(httpRequest, res.GetResult());
auto res2 = ptr->SignRequest(httpRequest, ctx);

EXPECT_EQ(res2.IsSuccess(), true);
EXPECT_EQ(res2.GetResult()->GetSigningAccessKey(), "dummyAccessId");
Expand Down Expand Up @@ -287,15 +287,15 @@ TEST_F(SmithyClientTest, testSigV4a) {
ctx.m_pRequest = nullptr;

auto res = ptr->SelectAuthSchemeOption(ctx);

EXPECT_EQ(res.IsSuccess(), true);
EXPECT_EQ(res.GetResult().schemeId, key);
ctx.m_authSchemeOption = res.GetResultWithOwnership();

Aws::String uri{"https://treasureisland-cb93079d-24a0-4862-8es2-88456ead.xyz.amazonaws.com"};

std::shared_ptr<Aws::Http::HttpRequest> httpRequest(Aws::Http::CreateHttpRequest(uri, Aws::Http::HttpMethod::HTTP_GET, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));

auto res2 = ptr->SignRequest(httpRequest, res.GetResult());
auto res2 = ptr->SignRequest(httpRequest, ctx);

EXPECT_EQ(res2.IsSuccess(), true);

Expand Down Expand Up @@ -333,9 +333,9 @@ TEST_F(SmithyClientTest, bearer)
ctx.m_pRequest = nullptr;

auto res = ptr->SelectAuthSchemeOption(ctx);

EXPECT_EQ(res.IsSuccess(), true);
EXPECT_EQ(res.GetResult().schemeId, key);
ctx.m_authSchemeOption = res.GetResultWithOwnership();

Aws::String uri{
"https://"
Expand All @@ -346,7 +346,7 @@ TEST_F(SmithyClientTest, bearer)
uri, Aws::Http::HttpMethod::HTTP_GET,
Aws::Utils::Stream::DefaultResponseStreamFactoryMethod));

auto res2 = ptr->SignRequest(httpRequest, res.GetResult());
auto res2 = ptr->SignRequest(httpRequest, ctx);

EXPECT_EQ(res2.IsSuccess(), true);

Expand Down

0 comments on commit f819175

Please sign in to comment.