Skip to content

Commit

Permalink
grpc: Add AWS IAM grpc credentials extension (envoyproxy#7532)
Browse files Browse the repository at this point in the history
Signed-off-by: Scott LaVigne <[email protected]>
  • Loading branch information
lavignes authored and mattklein123 committed Aug 9, 2019
1 parent 04477ca commit c92b8ba
Show file tree
Hide file tree
Showing 16 changed files with 439 additions and 1 deletion.
3 changes: 3 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ extensions/filters/common/original_src @snowp @klarose
/*/extensions/filters/http/dynamic_forward_proxy @mattklein123 @alyssawilk
# omit_canary_hosts retry predicate
/*/extensions/retry/host/omit_canary_hosts @sriduth @snowp
# aws_iam grpc credentials
/*/extensions/grpc_credentials/aws_iam @lavignes @mattklein123
/*/extensions/filters/http/common/aws @lavignes @mattklein123
# adaptive concurrency limit extension.
/*/extensions/filters/http/adaptive_concurrency @tonya11en @mattklein123
# http inspector
Expand Down
1 change: 1 addition & 0 deletions api/docs/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ proto_library(
"//envoy/config/filter/network/thrift_proxy/v2alpha1:thrift_proxy",
"//envoy/config/filter/thrift/rate_limit/v2alpha1:rate_limit",
"//envoy/config/filter/thrift/router/v2alpha1:router",
"//envoy/config/grpc_credential/v2alpha:aws_iam",
"//envoy/config/grpc_credential/v2alpha:file_based_metadata",
"//envoy/config/health_checker/redis/v2:redis",
"//envoy/config/metrics/v2:metrics_service",
Expand Down
10 changes: 10 additions & 0 deletions api/envoy/config/grpc_credential/v2alpha/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,13 @@ api_go_proto_library(
"//envoy/api/v2/core:base_go_proto",
],
)

api_proto_library_internal(
name = "aws_iam",
srcs = ["aws_iam.proto"],
)

api_go_proto_library(
name = "aws_iam",
proto = ":aws_iam",
)
29 changes: 29 additions & 0 deletions api/envoy/config/grpc_credential/v2alpha/aws_iam.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
syntax = "proto3";

// [#protodoc-title: Grpc Credentials AWS IAM]
// Configuration for AWS IAM Grpc Credentials Plugin

package envoy.config.grpc_credential.v2alpha;

option java_outer_classname = "AwsIamProto";
option java_package = "io.envoyproxy.envoy.config.grpc_credential.v2alpha";
option java_multiple_files = true;
option go_package = "v2alpha";

import "validate/validate.proto";

message AwsIamConfig {
// The `service namespace
// <https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces>`_
// of the Grpc endpoint.
//
// Example: appmesh
string service_name = 1 [(validate.rules).string.min_bytes = 1];

// The `region <https://docs.aws.amazon.com/general/latest/gr/rande.html>`_ hosting the Grpc
// endpoint. If unspecified, the extension will use the value in the ``AWS_REGION`` environment
// variable.
//
// Example: us-west-2
string region = 2;
}
1 change: 1 addition & 0 deletions docs/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ PROTO_RST="
/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto.rst
/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto.rst
/envoy/config/filter/thrift/router/v2alpha1/router/envoy/config/filter/thrift/router/v2alpha1/router.proto.rst
/envoy/config/grpc_credential/v2alpha/aws_iam/envoy/config/grpc_credential/v2alpha/aws_iam.proto.rst
/envoy/config/health_checker/redis/v2/redis/envoy/config/health_checker/redis/v2/redis.proto.rst
/envoy/config/overload/v2alpha/overload/envoy/config/overload/v2alpha/overload.proto.rst
/envoy/config/rbac/v2/rbac/envoy/config/rbac/v2/rbac.proto.rst
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v2/config/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ Extensions
resource_monitor/resource_monitor
common/common
cluster/cluster
grpc_credential/grpc_credential
8 changes: 8 additions & 0 deletions docs/root/api-v2/config/grpc_credential/grpc_credential.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Grpc Credentials
================

.. toctree::
:glob:
:maxdepth: 1

v2alpha/*
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Version history
* config: changed the default value of :ref:`initial_fetch_timeout <envoy_api_field_core.ConfigSource.initial_fetch_timeout>` from 0s to 15s. This is a change in behaviour in the sense that Envoy will move to the next initialization phase, even if the first config is not delivered in 15s. Refer to :ref:`initialization process <arch_overview_initialization>` for more details.
* config: added stat :ref:`init_fetch_timeout <config_cluster_manager_cds>`.
* fault: added overrides for default runtime keys in :ref:`HTTPFault <envoy_api_msg_config.filter.http.fault.v2.HTTPFault>` filter.
* grpc: added :ref:`AWS IAM grpc credentials extension <envoy_api_file_envoy/config/grpc_credential/v2alpha/aws_iam.proto>` for AWS-managed xDS.
* grpc-json: added support for :ref:`ignoring unknown query parameters<envoy_api_field_config.filter.http.transcoder.v2.GrpcJsonTranscoder.ignore_unknown_query_parameters>`.
* header to metadata: added :ref:`PROTOBUF_VALUE <envoy_api_enum_value_config.filter.http.header_to_metadata.v2.Config.ValueType.PROTOBUF_VALUE>` and :ref:`ValueEncode <envoy_api_enum_config.filter.http.header_to_metadata.v2.Config.ValueEncode>` to support protobuf Value and Base64 encoding.
* http: added the ability to reject HTTP/1.1 requests with invalid HTTP header values, using the runtime feature `envoy.reloadable_features.strict_header_validation`.
Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ EXTENSIONS = {
#

"envoy.grpc_credentials.file_based_metadata": "//source/extensions/grpc_credentials/file_based_metadata:config",
"envoy.grpc_credentials.aws_iam": "//source/extensions/grpc_credentials/aws_iam:config",

#
# Health checkers
Expand Down
33 changes: 33 additions & 0 deletions source/extensions/grpc_credentials/aws_iam/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
licenses(["notice"]) # Apache 2

# AWS IAM gRPC Credentials

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_package",
)

envoy_package()

envoy_cc_library(
name = "config",
srcs = ["config.cc"],
hdrs = ["config.h"],
external_deps = ["grpc"],
deps = [
"//include/envoy/grpc:google_grpc_creds_interface",
"//include/envoy/registry",
"//source/common/common:assert_lib",
"//source/common/config:utility_lib",
"//source/common/grpc:google_grpc_creds_lib",
"//source/common/http:message_lib",
"//source/common/http:utility_lib",
"//source/extensions/filters/http/common/aws:credentials_provider_impl_lib",
"//source/extensions/filters/http/common/aws:region_provider_impl_lib",
"//source/extensions/filters/http/common/aws:signer_impl_lib",
"//source/extensions/filters/http/common/aws:utility_lib",
"//source/extensions/grpc_credentials:well_known_names",
"@envoy_api//envoy/config/grpc_credential/v2alpha:aws_iam_cc",
],
)
148 changes: 148 additions & 0 deletions source/extensions/grpc_credentials/aws_iam/config.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#include "extensions/grpc_credentials/aws_iam/config.h"

#include "envoy/api/v2/core/grpc_service.pb.h"
#include "envoy/common/exception.h"
#include "envoy/config/grpc_credential/v2alpha/aws_iam.pb.validate.h"
#include "envoy/grpc/google_grpc_creds.h"
#include "envoy/registry/registry.h"

#include "common/config/utility.h"
#include "common/grpc/google_grpc_creds_impl.h"
#include "common/http/utility.h"
#include "common/protobuf/message_validator_impl.h"

#include "extensions/filters/http/common/aws/credentials_provider_impl.h"
#include "extensions/filters/http/common/aws/region_provider_impl.h"
#include "extensions/filters/http/common/aws/signer_impl.h"
#include "extensions/filters/http/common/aws/utility.h"

namespace Envoy {
namespace Extensions {
namespace GrpcCredentials {
namespace AwsIam {

std::shared_ptr<grpc::ChannelCredentials> AwsIamGrpcCredentialsFactory::getChannelCredentials(
const envoy::api::v2::core::GrpcService& grpc_service_config, Api::Api& api) {

const auto& google_grpc = grpc_service_config.google_grpc();
std::shared_ptr<grpc::ChannelCredentials> creds =
Grpc::CredsUtility::defaultSslChannelCredentials(grpc_service_config, api);

std::shared_ptr<grpc::CallCredentials> call_creds;
for (const auto& credential : google_grpc.call_credentials()) {
switch (credential.credential_specifier_case()) {
case envoy::api::v2::core::GrpcService::GoogleGrpc::CallCredentials::kFromPlugin: {
if (credential.from_plugin().name() == GrpcCredentialsNames::get().AwsIam) {
AwsIamGrpcCredentialsFactory credentials_factory;
const Envoy::ProtobufTypes::MessagePtr config_message =
Envoy::Config::Utility::translateToFactoryConfig(
credential.from_plugin(), ProtobufMessage::getNullValidationVisitor(),
credentials_factory);
const auto& config = Envoy::MessageUtil::downcastAndValidate<
const envoy::config::grpc_credential::v2alpha::AwsIamConfig&>(*config_message);
auto credentials_provider =
std::make_shared<HttpFilters::Common::Aws::DefaultCredentialsProviderChain>(
api, HttpFilters::Common::Aws::Utility::metadataFetcher);
auto signer = std::make_unique<HttpFilters::Common::Aws::SignerImpl>(
config.service_name(), getRegion(config), credentials_provider, api.timeSource());
std::shared_ptr<grpc::CallCredentials> new_call_creds = grpc::MetadataCredentialsFromPlugin(
std::make_unique<AwsIamHeaderAuthenticator>(std::move(signer)));
if (call_creds == nullptr) {
call_creds = new_call_creds;
} else {
call_creds = grpc::CompositeCallCredentials(call_creds, new_call_creds);
}
}
break;
}
default:
// unused credential types
continue;
}
}

if (call_creds != nullptr) {
return grpc::CompositeChannelCredentials(creds, call_creds);
}

return creds;
}

std::string AwsIamGrpcCredentialsFactory::getRegion(
const envoy::config::grpc_credential::v2alpha::AwsIamConfig& config) {
std::unique_ptr<HttpFilters::Common::Aws::RegionProvider> region_provider;
if (!config.region().empty()) {
region_provider =
std::make_unique<HttpFilters::Common::Aws::StaticRegionProvider>(config.region());
} else {
region_provider = std::make_unique<HttpFilters::Common::Aws::EnvironmentRegionProvider>();
}

if (!region_provider->getRegion().has_value()) {
throw EnvoyException("Could not determine AWS region. "
"If you are not running Envoy in EC2 or ECS, "
"provide the region in the plugin configuration.");
}

return *region_provider->getRegion();
}

grpc::Status
AwsIamHeaderAuthenticator::GetMetadata(grpc::string_ref service_url, grpc::string_ref method_name,
const grpc::AuthContext&,
std::multimap<grpc::string, grpc::string>* metadata) {

auto message = buildMessageToSign(absl::string_view(service_url.data(), service_url.length()),
absl::string_view(method_name.data(), method_name.length()));

try {
signer_->sign(message, false);
} catch (const EnvoyException& e) {
return grpc::Status(grpc::StatusCode::INTERNAL, e.what());
}

signedHeadersToMetadata(message.headers(), *metadata);

return grpc::Status::OK;
}

Http::RequestMessageImpl
AwsIamHeaderAuthenticator::buildMessageToSign(absl::string_view service_url,
absl::string_view method_name) {

const auto uri = fmt::format("{}/{}", service_url, method_name);
absl::string_view host;
absl::string_view path;
Http::Utility::extractHostPathFromUri(uri, host, path);

Http::RequestMessageImpl message;
message.headers().insertMethod().value().setReference(Http::Headers::get().MethodValues.Post);
message.headers().insertHost().value(host);
message.headers().insertPath().value(path);

return message;
}

void AwsIamHeaderAuthenticator::signedHeadersToMetadata(
const Http::HeaderMap& headers, std::multimap<grpc::string, grpc::string>& metadata) {

headers.iterate(
[](const Http::HeaderEntry& entry, void* context) -> Http::HeaderMap::Iterate {
auto* md = static_cast<std::multimap<grpc::string, grpc::string>*>(context);
const auto& key = entry.key().getStringView();
// Skip pseudo-headers
if (key.empty() || key[0] == ':') {
return Http::HeaderMap::Iterate::Continue;
}
md->emplace(key, entry.value().getStringView());
return Http::HeaderMap::Iterate::Continue;
},
&metadata);
}

REGISTER_FACTORY(AwsIamGrpcCredentialsFactory, Grpc::GoogleGrpcCredentialsFactory);

} // namespace AwsIam
} // namespace GrpcCredentials
} // namespace Extensions
} // namespace Envoy
62 changes: 62 additions & 0 deletions source/extensions/grpc_credentials/aws_iam/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include "envoy/config/grpc_credential/v2alpha/aws_iam.pb.validate.h"
#include "envoy/grpc/google_grpc_creds.h"
#include "envoy/http/header_map.h"

#include "common/http/message_impl.h"

#include "extensions/filters/http/common/aws/signer.h"
#include "extensions/grpc_credentials/well_known_names.h"

namespace Envoy {
namespace Extensions {
namespace GrpcCredentials {
namespace AwsIam {

/**
* AWS IAM based gRPC channel credentials factory.
*/
class AwsIamGrpcCredentialsFactory : public Grpc::GoogleGrpcCredentialsFactory {
public:
std::shared_ptr<grpc::ChannelCredentials>
getChannelCredentials(const envoy::api::v2::core::GrpcService& grpc_service_config,
Api::Api& api) override;

Envoy::ProtobufTypes::MessagePtr createEmptyConfigProto() {
return std::make_unique<envoy::config::grpc_credential::v2alpha::AwsIamConfig>();
}

std::string name() const override { return GrpcCredentialsNames::get().AwsIam; }

private:
static std::string getRegion(const envoy::config::grpc_credential::v2alpha::AwsIamConfig& config);
};

/**
* Produce AWS IAM signature metadata for a gRPC call.
*/
class AwsIamHeaderAuthenticator : public grpc::MetadataCredentialsPlugin {
public:
AwsIamHeaderAuthenticator(HttpFilters::Common::Aws::SignerPtr signer)
: signer_(std::move(signer)) {}

grpc::Status GetMetadata(grpc::string_ref, grpc::string_ref, const grpc::AuthContext&,
std::multimap<grpc::string, grpc::string>* metadata) override;

bool IsBlocking() const override { return true; }

private:
static Http::RequestMessageImpl buildMessageToSign(absl::string_view service_url,
absl::string_view method_name);

static void signedHeadersToMetadata(const Http::HeaderMap& headers,
std::multimap<grpc::string, grpc::string>& metadata);

const HttpFilters::Common::Aws::SignerPtr signer_;
};

} // namespace AwsIam
} // namespace GrpcCredentials
} // namespace Extensions
} // namespace Envoy
2 changes: 2 additions & 0 deletions source/extensions/grpc_credentials/well_known_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class GrpcCredentialsNameValues {
const std::string AccessTokenExample = "envoy.grpc_credentials.access_token_example";
// File Based Metadata credentials
const std::string FileBasedMetadata = "envoy.grpc_credentials.file_based_metadata";
// AWS IAM
const std::string AwsIam = "envoy.grpc_credentials.aws_iam";
};

using GrpcCredentialsNames = ConstSingleton<GrpcCredentialsNameValues>;
Expand Down
23 changes: 23 additions & 0 deletions test/extensions/grpc_credentials/aws_iam/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_test",
"envoy_package",
"envoy_select_google_grpc",
)

envoy_package()

envoy_cc_test(
name = "aws_iam_grpc_credentials_test",
srcs = envoy_select_google_grpc(["aws_iam_grpc_credentials_test.cc"]),
data = ["//test/config/integration/certs"],
deps = [
"//source/extensions/grpc_credentials:well_known_names",
"//source/extensions/grpc_credentials/aws_iam:config",
"//test/common/grpc:grpc_client_integration_test_harness_lib",
"//test/integration:integration_lib",
"@envoy_api//envoy/config/grpc_credential/v2alpha:aws_iam_cc",
] + envoy_select_google_grpc(["//source/common/grpc:google_async_client_lib"]),
)
Loading

0 comments on commit c92b8ba

Please sign in to comment.