Line data Source code
1 : #include "source/extensions/grpc_credentials/aws_iam/config.h" 2 : 3 : #include "envoy/common/exception.h" 4 : #include "envoy/config/core/v3/grpc_service.pb.h" 5 : #include "envoy/config/grpc_credential/v3/aws_iam.pb.h" 6 : #include "envoy/config/grpc_credential/v3/aws_iam.pb.validate.h" 7 : #include "envoy/grpc/google_grpc_creds.h" 8 : #include "envoy/registry/registry.h" 9 : 10 : #include "source/common/config/utility.h" 11 : #include "source/common/grpc/google_grpc_creds_impl.h" 12 : #include "source/common/http/utility.h" 13 : #include "source/common/protobuf/message_validator_impl.h" 14 : #include "source/extensions/common/aws/credentials_provider_impl.h" 15 : #include "source/extensions/common/aws/region_provider_impl.h" 16 : #include "source/extensions/common/aws/sigv4_signer_impl.h" 17 : #include "source/extensions/common/aws/utility.h" 18 : 19 : namespace Envoy { 20 : namespace Extensions { 21 : namespace GrpcCredentials { 22 : namespace AwsIam { 23 : 24 : std::shared_ptr<grpc::ChannelCredentials> AwsIamGrpcCredentialsFactory::getChannelCredentials( 25 0 : const envoy::config::core::v3::GrpcService& grpc_service_config, Api::Api& api) { 26 : 27 0 : const auto& google_grpc = grpc_service_config.google_grpc(); 28 0 : std::shared_ptr<grpc::ChannelCredentials> creds = 29 0 : Grpc::CredsUtility::defaultSslChannelCredentials(grpc_service_config, api); 30 : 31 0 : std::shared_ptr<grpc::CallCredentials> call_creds; 32 0 : for (const auto& credential : google_grpc.call_credentials()) { 33 0 : switch (credential.credential_specifier_case()) { 34 0 : case envoy::config::core::v3::GrpcService::GoogleGrpc::CallCredentials:: 35 0 : CredentialSpecifierCase::kFromPlugin: { 36 0 : if (credential.from_plugin().name() == "envoy.grpc_credentials.aws_iam") { 37 0 : AwsIamGrpcCredentialsFactory credentials_factory; 38 : // We don't deal with validation failures here at runtime today, see 39 : // https://github.com/envoyproxy/envoy/issues/8010. 40 0 : const Envoy::ProtobufTypes::MessagePtr config_message = 41 0 : Envoy::Config::Utility::translateToFactoryConfig( 42 0 : credential.from_plugin(), ProtobufMessage::getNullValidationVisitor(), 43 0 : credentials_factory); 44 0 : const auto& config = Envoy::MessageUtil::downcastAndValidate< 45 0 : const envoy::config::grpc_credential::v3::AwsIamConfig&>( 46 0 : *config_message, ProtobufMessage::getNullValidationVisitor()); 47 0 : const auto region = getRegion(config); 48 : // TODO(suniltheta): Due to the reasons explained in 49 : // https://github.com/envoyproxy/envoy/issues/27586 this aws iam plugin is not able to 50 : // utilize http async client to fetch AWS credentials. For time being this is still using 51 : // libcurl to fetch the credentials. To fully get rid of curl, need to address the below 52 : // usage of AWS credentials common utils. Until then we are setting nullopt for server 53 : // factory context. 54 0 : auto credentials_provider = std::make_shared<Common::Aws::DefaultCredentialsProviderChain>( 55 0 : api, absl::nullopt /*Empty factory context*/, region, 56 0 : Common::Aws::Utility::fetchMetadata); 57 0 : auto signer = std::make_unique<Common::Aws::SigV4SignerImpl>( 58 0 : config.service_name(), region, credentials_provider, api.timeSource(), 59 : // TODO: extend API to allow specifying header exclusion. ref: 60 : // https://github.com/envoyproxy/envoy/pull/18998 61 0 : Common::Aws::AwsSigningHeaderExclusionVector{}); 62 0 : std::shared_ptr<grpc::CallCredentials> new_call_creds = grpc::MetadataCredentialsFromPlugin( 63 0 : std::make_unique<AwsIamHeaderAuthenticator>(std::move(signer))); 64 0 : if (call_creds == nullptr) { 65 0 : call_creds = new_call_creds; 66 0 : } else { 67 0 : call_creds = grpc::CompositeCallCredentials(call_creds, new_call_creds); 68 0 : } 69 0 : } 70 0 : break; 71 0 : } 72 0 : default: 73 : // unused credential types 74 0 : continue; 75 0 : } 76 0 : } 77 : 78 0 : if (call_creds != nullptr) { 79 0 : return grpc::CompositeChannelCredentials(creds, call_creds); 80 0 : } 81 : 82 0 : return creds; 83 0 : } 84 : 85 : std::string AwsIamGrpcCredentialsFactory::getRegion( 86 0 : const envoy::config::grpc_credential::v3::AwsIamConfig& config) { 87 0 : Common::Aws::RegionProviderPtr region_provider; 88 0 : if (!config.region().empty()) { 89 0 : region_provider = std::make_unique<Common::Aws::StaticRegionProvider>(config.region()); 90 0 : } else { 91 0 : region_provider = std::make_unique<Common::Aws::EnvironmentRegionProvider>(); 92 0 : } 93 : 94 0 : if (!region_provider->getRegion().has_value()) { 95 0 : throw EnvoyException("Could not determine AWS region. " 96 0 : "If you are not running Envoy in EC2 or ECS, " 97 0 : "provide the region in the plugin configuration."); 98 0 : } 99 : 100 0 : return *region_provider->getRegion(); 101 0 : } 102 : 103 : grpc::Status 104 : AwsIamHeaderAuthenticator::GetMetadata(grpc::string_ref service_url, grpc::string_ref method_name, 105 : const grpc::AuthContext&, 106 0 : std::multimap<grpc::string, grpc::string>* metadata) { 107 : 108 0 : auto message = buildMessageToSign(absl::string_view(service_url.data(), service_url.length()), 109 0 : absl::string_view(method_name.data(), method_name.length())); 110 : 111 0 : TRY_NEEDS_AUDIT { signer_->sign(message, false); } 112 0 : END_TRY catch (const EnvoyException& e) { 113 0 : return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); 114 0 : } 115 : 116 0 : signedHeadersToMetadata(message.headers(), *metadata); 117 : 118 0 : return grpc::Status::OK; 119 0 : } 120 : 121 : Http::RequestMessageImpl 122 : AwsIamHeaderAuthenticator::buildMessageToSign(absl::string_view service_url, 123 0 : absl::string_view method_name) { 124 : 125 0 : const auto uri = fmt::format("{}/{}", service_url, method_name); 126 0 : absl::string_view host; 127 0 : absl::string_view path; 128 0 : Http::Utility::extractHostPathFromUri(uri, host, path); 129 : 130 0 : Http::RequestMessageImpl message; 131 0 : message.headers().setReferenceMethod(Http::Headers::get().MethodValues.Post); 132 0 : message.headers().setHost(host); 133 0 : message.headers().setPath(path); 134 : 135 0 : return message; 136 0 : } 137 : 138 : void AwsIamHeaderAuthenticator::signedHeadersToMetadata( 139 0 : const Http::HeaderMap& headers, std::multimap<grpc::string, grpc::string>& metadata) { 140 : 141 0 : headers.iterate([&metadata](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate { 142 0 : const auto& key = entry.key().getStringView(); 143 : // Skip pseudo-headers 144 0 : if (key.empty() || key[0] == ':') { 145 0 : return Http::HeaderMap::Iterate::Continue; 146 0 : } 147 0 : metadata.emplace(key, entry.value().getStringView()); 148 0 : return Http::HeaderMap::Iterate::Continue; 149 0 : }); 150 0 : } 151 : 152 : REGISTER_FACTORY(AwsIamGrpcCredentialsFactory, Grpc::GoogleGrpcCredentialsFactory); 153 : 154 : } // namespace AwsIam 155 : } // namespace GrpcCredentials 156 : } // namespace Extensions 157 : } // namespace Envoy