diff --git a/src/aws-cpp-sdk-core/include/smithy/identity/resolver/AwsBearerTokenIdentityResolver.h b/src/aws-cpp-sdk-core/include/smithy/identity/resolver/AwsBearerTokenIdentityResolver.h index 5c387797e15..31ba730b330 100644 --- a/src/aws-cpp-sdk-core/include/smithy/identity/resolver/AwsBearerTokenIdentityResolver.h +++ b/src/aws-cpp-sdk-core/include/smithy/identity/resolver/AwsBearerTokenIdentityResolver.h @@ -5,15 +5,52 @@ #pragma once #include - #include +#include +#include + namespace smithy { + + static const char SSO_DEFAULT_BEARER_TOKEN_PROVIDER_CHAIN_LOG_TAG[] = "SSOBearerTokenProvider"; + static const char BEARER_TOKEN_PROVIDER_CHAIN_LOG_TAG[] = "BearerTokenProvider"; + class AwsBearerTokenIdentityResolver : public IdentityResolverBase { public: using IdentityT = AwsBearerTokenIdentity; virtual ~AwsBearerTokenIdentityResolver() = default; - ResolveIdentityFutureOutcome getIdentity(const IdentityProperties& identityProperties, const AdditionalParameters& additionalParameters) override = 0; + explicit AwsBearerTokenIdentityResolver(const Aws::Vector >& providerChain) : m_providerChainLegacy{providerChain} + { + + } + + ResolveIdentityFutureOutcome getIdentity(const IdentityProperties& identityProperties, const AdditionalParameters& additionalParameters) override + { + for (auto&& bearerTokenProvider : m_providerChainLegacy) + { + if(!bearerTokenProvider) { + AWS_LOGSTREAM_FATAL(BEARER_TOKEN_PROVIDER_CHAIN_LOG_TAG, + "Unexpected nullptr in DefaultBearerTokenProviderChain::m_providerChain"); + return Aws::Client::AWSError(Aws::Client::CoreErrors::INVALID_PARAMETER_VALUE, "", "Unexpected nullptr in BearerTokenProviderChain::m_providerChain", false); + } + auto bearerToken = bearerTokenProvider->GetAWSBearerToken(); + if(!bearerToken.IsExpiredOrEmpty()) + { + return ResolveIdentityFutureOutcome(std::move( Aws::MakeUnique( BEARER_TOKEN_PROVIDER_CHAIN_LOG_TAG, bearerToken.GetToken(), bearerToken.GetExpiration()))); + } + } + } + protected: + Aws::Vector > m_providerChainLegacy; + }; + + class DefaultAwsBearerTokenIdentityResolver : public AwsBearerTokenIdentityResolver { + public: + using IdentityT = AwsBearerTokenIdentity; + virtual ~DefaultAwsBearerTokenIdentityResolver() = default; + + DefaultAwsBearerTokenIdentityResolver() : AwsBearerTokenIdentityResolver({Aws::MakeShared(SSO_DEFAULT_BEARER_TOKEN_PROVIDER_CHAIN_LOG_TAG)}){}; + }; } \ No newline at end of file diff --git a/src/aws-cpp-sdk-core/include/smithy/identity/signer/built-in/BearerTokenSigner.h b/src/aws-cpp-sdk-core/include/smithy/identity/signer/built-in/BearerTokenSigner.h index ce8cde8f0e2..403aea01337 100644 --- a/src/aws-cpp-sdk-core/include/smithy/identity/signer/built-in/BearerTokenSigner.h +++ b/src/aws-cpp-sdk-core/include/smithy/identity/signer/built-in/BearerTokenSigner.h @@ -6,7 +6,7 @@ #pragma once #include -#include +#include #include #include @@ -18,202 +18,39 @@ #include namespace smithy { - static const char* UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD"; - static const char* EMPTY_STRING_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - static const char v4AsymmetricLogTag[] = "AWSAuthSymmetricV4Signer"; - static const char* USER_AGENT = "user-agent"; - static const char* X_AMZN_TRACE_ID = "x-amzn-trace-id"; + static const char AUTHORIZATION_HEADER[] = "authorization"; + /** * A smithy SigV4 signer wrapper on top of legacy SDK SigV4 signer * TODO: refactor into own signer using smithy design */ - class AwsSigV4aSigner : public AwsSignerBase { + class BearerTokenSigner : public AwsSignerBase { public: - using SigV4aAuthSchemeParameters = smithy::DefaultAuthSchemeResolverParameters; - explicit AwsSigV4aSigner(const Aws::String& serviceName, const Aws::String& region) - : m_serviceName(serviceName), m_region(region) + //using SigV4aAuthSchemeParameters = smithy::DefaultAuthSchemeResolverParameters; + explicit BearerTokenSigner() { } - SigningFutureOutcome sign(std::shared_ptr httpRequest, const AwsCredentialIdentityBase& identity, SigningProperties properties) override + SigningFutureOutcome sign(std::shared_ptr httpRequest, const smithy::AwsBearerTokenIdentityBase& identity, SigningProperties properties) override { - - auto signPayloadIt = properties.find("SignPayload"); - bool signPayload = signPayloadIt != properties.end() ? signPayloadIt->second.get() == "true" : false; - - assert(httpRequest); - assert(identity.expiration().has_value()); - - auto &request = *httpRequest; - - auto crtCredentials = Aws::MakeShared(v4AsymmetricLogTag, - Aws::Crt::ByteCursorFromCString(identity.accessKeyId().c_str()), - Aws::Crt::ByteCursorFromCString(identity.secretAccessKey().c_str()), - Aws::Crt::ByteCursorFromCString((*identity.sessionToken()).c_str()), - (*identity.expiration()).Seconds()); - - Aws::Crt::Auth::AwsSigningConfig awsSigningConfig; - - bool success = createAwsSigningConfig(crtCredentials, request, awsSigningConfig, signPayload); - - if(!success) + if(Aws::Http::Scheme::HTTPS != httpRequest->GetUri().GetScheme()) { - AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, "Failed to get Auth configuration"); - - return SigningError(Aws::Client::CoreErrors::MEMORY_ALLOCATION, "", "Failed to get Auth configuration", false); + // Clients MUST always use TLS (https) or equivalent transport security + // when making requests with bearer tokens. + // https://datatracker.ietf.org/doc/html/rfc6750 + AWS_LOGSTREAM_ERROR(LOGGING_TAG, "HTTPS scheme must be used with a bearer token authorization"); + return SigningError(Aws::Client::CoreErrors::INVALID_PARAMETER_VALUE, "", "Failed to sign the request with bearer", false); } - std::shared_ptr crtHttpRequest = request.ToCrtHttpRequest(); - - auto sigv4HttpRequestSigner = Aws::MakeShared(v4AsymmetricLogTag); - //This is an async call, so we need to wait till we have received an outcome - Aws::String errorMessage; - bool processed = false; - //producer function - sigv4HttpRequestSigner->SignRequest(crtHttpRequest, awsSigningConfig, - [&request, &success, &errorMessage, &processed, this](const std::shared_ptr& signedCrtHttpRequest, int errorCode) { - std::unique_lock lock(m_mutex); - m_cv.wait(lock, [&]{ return !processed; }); - success = (errorCode == AWS_ERROR_SUCCESS); - if (success) - { - if (m_signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaHeaders) - { - for (size_t i = 0; i < signedCrtHttpRequest->GetHeaderCount(); i++) - { - Aws::Crt::Optional httpHeader = signedCrtHttpRequest->GetHeader(i); - request.SetHeaderValue(Aws::String(reinterpret_cast(httpHeader->name.ptr), httpHeader->name.len), - Aws::String(reinterpret_cast(httpHeader->value.ptr), httpHeader->value.len)); - } - } - else if (m_signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaQueryParams) - { - Aws::Http::URI newPath(reinterpret_cast(signedCrtHttpRequest->GetPath()->ptr)); - request.GetUri().SetQueryString(newPath.GetQueryString()); - } - else - { - errorMessage = "No action to take when signature type is neither \"HttpRequestViaHeaders\" nor \"HttpRequestViaQueryParams\""; - AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, errorMessage); - success = false; - } - } - else - { - Aws::OStringStream errStream; - errStream << "Encountered internal error during signing process with AWS signature version 4 (Asymmetric):" << aws_error_str(errorCode); - errorMessage = errStream.str(); - AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, errorMessage); - } - - processed = true; - m_cv.notify_all(); - } - ); - - //consumer - { - std::unique_lock lock(m_mutex); - m_cv.wait(lock, [&]{ return processed; }); - - } + httpRequest->SetHeaderValue(AUTHORIZATION_HEADER, "Bearer " + identity.token()); + - return success? SigningFutureOutcome(std::move(httpRequest)) : SigningError(Aws::Client::CoreErrors::MEMORY_ALLOCATION, "", "Failed to sign the request with sigv4", false); + return SigningFutureOutcome(std::move(httpRequest)); } - - - virtual ~AwsSigV4aSigner() {}; + virtual ~BearerTokenSigner() {}; protected: - - bool createAwsSigningConfig( - std::shared_ptr& crtCredentials, - const Aws::Http::HttpRequest& request, - Aws::Crt::Auth::AwsSigningConfig& awsSigningConfig, - bool signBody) const - { - awsSigningConfig.SetSigningAlgorithm(static_cast(Aws::Auth::AWSSigningAlgorithm::ASYMMETRIC_SIGV4)); - awsSigningConfig.SetSignatureType(m_signatureType); - awsSigningConfig.SetRegion(m_region.c_str()); - awsSigningConfig.SetService(m_region.c_str()); - awsSigningConfig.SetSigningTimepoint(GetSigningTimestamp().UnderlyingTimestamp()); - awsSigningConfig.SetUseDoubleUriEncode(m_urlEscape); - awsSigningConfig.SetShouldNormalizeUriPath(true); - awsSigningConfig.SetOmitSessionToken(false); - awsSigningConfig.SetShouldSignHeaderUserData(reinterpret_cast(const_cast*>(&m_unsignedHeaders))); - awsSigningConfig.SetShouldSignHeaderCallback([](const Aws::Crt::ByteCursor *name, void *user_data) { - Aws::Set* unsignedHeaders = static_cast*>(user_data); - Aws::String headerKey(reinterpret_cast(name->ptr), name->len); - return unsignedHeaders->find(Aws::Utils::StringUtils::ToLower(headerKey.c_str())) == unsignedHeaders->cend(); - }); - if (m_signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaHeaders) - { - Aws::String payloadHash(UNSIGNED_PAYLOAD); - if(signBody || request.GetUri().GetScheme() != Aws::Http::Scheme::HTTPS) - { - if (!request.GetContentBody()) - { - AWS_LOGSTREAM_DEBUG(v4AsymmetricLogTag, "Using cached empty string sha256 " << EMPTY_STRING_SHA256 << " because payload is empty."); - payloadHash = EMPTY_STRING_SHA256; - } - else - { - // The hash will be calculated from the payload during signing. - payloadHash = {}; - } - } - else - { - AWS_LOGSTREAM_DEBUG(v4AsymmetricLogTag, "Note: Http payloads are not being signed. signPayloads=" << signBody - << " http scheme=" << Aws::Http::SchemeMapper::ToString(request.GetUri().GetScheme())); - } - awsSigningConfig.SetSignedBodyValue(payloadHash.c_str()); - awsSigningConfig.SetSignedBodyHeader(m_includeSha256HashHeader ? Aws::Crt::Auth::SignedBodyHeaderType::XAmzContentSha256 : Aws::Crt::Auth::SignedBodyHeaderType::None); - } - else if (m_signatureType == Aws::Crt::Auth::SignatureType::HttpRequestViaQueryParams) - { - if (ServiceRequireUnsignedPayload(m_serviceName)) - { - awsSigningConfig.SetSignedBodyValue(UNSIGNED_PAYLOAD); - } - else - { - awsSigningConfig.SetSignedBodyValue(EMPTY_STRING_SHA256); - } - } - else - { - AWS_LOGSTREAM_ERROR(v4AsymmetricLogTag, "The signature type should be either \"HttpRequestViaHeaders\" or \"HttpRequestViaQueryParams\""); - return false; - } - awsSigningConfig.SetExpirationInSeconds(static_cast(m_expirationTimeInSeconds)); - awsSigningConfig.SetCredentials(crtCredentials); - return true; - } - - - bool ServiceRequireUnsignedPayload(const Aws::String& serviceName) const - { - // S3 uses a magic string (instead of the empty string) for its body hash for presigned URLs as outlined here: - // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html - // this is true for PUT, POST, GET, DELETE and HEAD operations. - // However, other services (for example RDS) implement the specification as outlined here: - // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html - // which states that body-less requests should use the empty-string SHA256 hash. - return "s3" == serviceName || "s3-object-lambda" == serviceName; - } - - Aws::String m_serviceName; - Aws::String m_region; - //params that can be exposed later - long long m_expirationTimeInSeconds{0}; - const bool m_includeSha256HashHeader{true}; - const bool m_urlEscape{true}; - const Aws::Set m_unsignedHeaders{USER_AGENT,X_AMZN_TRACE_ID}; - const Aws::Crt::Auth::SignatureType m_signatureType{Aws::Crt::Auth::SignatureType::HttpRequestViaQueryParams}; - std::condition_variable m_cv; - std::mutex m_mutex; }; }