Skip to content

Commit

Permalink
Adds support for inline credential
Browse files Browse the repository at this point in the history
Signed-off-by: Takeshi Yoneda <[email protected]>
  • Loading branch information
mathetake committed Oct 1, 2024
1 parent c6fd461 commit 53c3873
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 16 deletions.
28 changes: 22 additions & 6 deletions api/envoy/extensions/common/aws/v3/credential_provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ syntax = "proto3";
package envoy.extensions.common.aws.v3;

import "udpa/annotations/status.proto";
import "udpa/annotations/versioning.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.common.aws.v3";
option java_outer_classname = "AwsCommonProto";
option java_outer_classname = "CredentialProviderProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/common/aws/v3;awsv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;
Expand All @@ -20,16 +19,33 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
// by the control plane.
message AwsCredentialProvider {
oneof provider {
AssumeRoleWithWebIdentity assume_role_with_web_identity = 1;
// The option to use `AssumeRoleWithWebIdentity <https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html>`
AssumeRoleWithWebIdentityCredentialProvider assume_role_with_web_identity = 1;

// The option to use an inline credential.
InlineCredentialProvider inline_credential = 2;
}
}

// Configuration to use an inline AWS credential. This is an equivalent to setting the well-known
// environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and the optional `AWS_SESSION_TOKEN`.
message InlineCredentialProvider {
// The AWS access key ID.
string access_key_id = 1 [(validate.rules).string = {min_len: 1}];

// The AWS secret access key.
string secret_access_key = 2 [(validate.rules).string = {min_len: 1}];

// The AWS session token. This is optional.
string session_token = 3;
}

// Configuration to use `AssumeRoleWithWebIdentity <https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html>`
// to get AWS credentials.
message AssumeRoleWithWebIdentity {
message AssumeRoleWithWebIdentityCredentialProvider {
// The ARN of the role to assume.
string role_arn = 1;

// The identity token that is provided by the identity provider to assume the role.
string identity_token = 2;
// The web identity token that is provided by the identity provider to assume the role.
string web_identity_token = 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Credentials
-----------

The filter uses a number of different credentials providers to obtain an AWS access key ID, AWS secret access key, and AWS session token.
It moves through the credentials providers in the order described below, stopping when one of them returns an access key ID and a
By default, it moves through the credentials providers in the order described below, stopping when one of them returns an access key ID and a
secret access key (the session token is optional).

1. Environment variables. The environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN`` are used.
Expand Down Expand Up @@ -46,6 +46,9 @@ secret access key (the session token is optional).
The static internal cluster will still be added even if initially ``envoy.reloadable_features.use_http_client_to_fetch_aws_credentials`` is
not set so that subsequently if the reloadable feature is set to ``true`` the cluster config is available to fetch the credentials.

Alternatively, each AWS filter (either AWS Request Signing or AWS Lambda) has its own optional configuration to specify the source of the credentials. For example, AWS Request Signing filter
has :ref:`credential_provider <envoy_v3_api_field_extensions.filters.http.aws_request_signing.v3.AwsRequestSigning.credential_provider>` field.

Statistics
----------

Expand Down
22 changes: 16 additions & 6 deletions source/extensions/common/aws/credentials_provider_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1015,23 +1015,33 @@ DefaultCredentialsProviderChain::DefaultCredentialsProviderChain(
}
}

CredentialsProviderSharedPtr createCredentialsProvideFromConfig(
CredentialsProviderSharedPtr createCredentialsProviderFromConfig(
Server::Configuration::ServerFactoryContext& context, absl::string_view region,
const envoy::extensions::common::aws::v3::AwsCredentialProvider& config) {
if (config.has_assume_role_with_web_identity()) {
switch (config.provider_case()) {
case envoy::extensions::common::aws::v3::AwsCredentialProvider::ProviderCase::
kAssumeRoleWithWebIdentity: {
const auto& web_identity = config.assume_role_with_web_identity();
const std::string& role_arn = web_identity.role_arn();
const std::string& token = web_identity.identity_token();
const auto sts_endpoint = Utility::getSTSEndpoint(region) + ":443";
const auto cluster_name = stsClusterName(region);
const std::string& token = web_identity.web_identity_token();
const std::string sts_endpoint = Utility::getSTSEndpoint(region) + ":443";
const std::string cluster_name = stsClusterName(region);
const std::string role_session_name = sessionName(context.api());
const auto refresh_state = MetadataFetcher::MetadataReceiver::RefreshState::FirstRefresh;
// This "two seconds" is a bit arbitrary, but matches the other places in the codebase.
const auto initialization_timer = std::chrono::seconds(2);
return std::make_shared<WebIdentityCredentialsProvider>(
context.api(), context, Extensions::Common::Aws::Utility::fetchMetadata,
MetadataFetcher::create, "", token, sts_endpoint, role_arn, role_session_name,
refresh_state, initialization_timer, cluster_name);
} else {
}
case envoy::extensions::common::aws::v3::AwsCredentialProvider::ProviderCase::kInlineCredential: {
const auto& inline_credential = config.inline_credential();
return std::make_shared<InlineCredentialProvider>(inline_credential.access_key_id(),
inline_credential.secret_access_key(),
inline_credential.session_token());
}
case envoy::extensions::common::aws::v3::AwsCredentialProvider::ProviderCase::PROVIDER_NOT_SET:
return nullptr;
}
}
Expand Down
21 changes: 20 additions & 1 deletion source/extensions/common/aws/credentials_provider_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ class WebIdentityCredentialsProvider : public MetadataCredentialsProviderBase,
void onMetadataSuccess(const std::string&& body) override;
void onMetadataError(Failure reason) override;

const std::string& tokenForTesting() const { return token_; }
const std::string& roleArnForTesting() const { return role_arn_; }

private:
// token_ and token_file_path_ are mutually exclusive. If token_ is set, token_file_path_ is not
// used.
Expand Down Expand Up @@ -470,11 +473,27 @@ class DefaultCredentialsProviderChain : public CredentialsProviderChain,
}
};

/**
* Credential provider based on an inline credential.
*/
class InlineCredentialProvider : public CredentialsProvider {
public:
explicit InlineCredentialProvider(absl::string_view access_key_id,
absl::string_view secret_access_key,
absl::string_view session_token)
: credentials_(access_key_id, secret_access_key, session_token) {}

Credentials getCredentials() override { return credentials_; }

private:
const Credentials credentials_;
};

/**
* Create an AWS credentials provider from the proto configuration instead of using the default
* credentials provider chain.
*/
CredentialsProviderSharedPtr createCredentialsProvideFromConfig(
CredentialsProviderSharedPtr createCredentialsProviderFromConfig(
Server::Configuration::ServerFactoryContext& context, absl::string_view region,
const envoy::extensions::common::aws::v3::AwsCredentialProvider& config);

Expand Down
4 changes: 2 additions & 2 deletions source/extensions/filters/http/aws_request_signing/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ AwsRequestSigningFilterFactory::createFilterFactoryFromProtoTyped(

auto credentials_provider =
config.has_credential_provider()
? Extensions::Common::Aws::createCredentialsProvideFromConfig(
? Extensions::Common::Aws::createCredentialsProviderFromConfig(
server_context, region, config.credential_provider())
: std::make_shared<Extensions::Common::Aws::DefaultCredentialsProviderChain>(
server_context.api(), makeOptRef(server_context), region,
Expand Down Expand Up @@ -127,7 +127,7 @@ AwsRequestSigningFilterFactory::createRouteSpecificFilterConfigTyped(

auto credentials_provider =
per_route_config.aws_request_signing().has_credential_provider()
? Extensions::Common::Aws::createCredentialsProvideFromConfig(
? Extensions::Common::Aws::createCredentialsProviderFromConfig(
context, region, per_route_config.aws_request_signing().credential_provider())
: std::make_shared<Extensions::Common::Aws::DefaultCredentialsProviderChain>(
context.api(), makeOptRef(context), region,
Expand Down
1 change: 1 addition & 0 deletions test/extensions/common/aws/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ envoy_cc_test(
"//test/test_common:environment_lib",
"//test/test_common:simulated_time_system_lib",
"//test/test_common:test_runtime_lib",
"@envoy_api//envoy/extensions/common/aws/v3:pkg_cc_proto",
],
)

Expand Down
44 changes: 44 additions & 0 deletions test/extensions/common/aws/credentials_provider_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <ios>
#include <string>

#include "envoy/extensions/common/aws/v3/credential_provider.pb.h"

#include "source/extensions/common/aws/credentials_provider_impl.h"
#include "source/extensions/common/aws/metadata_fetcher.h"

Expand Down Expand Up @@ -2647,6 +2649,48 @@ TEST(CredentialsProviderChainTest, getCredentials_secondProviderReturns) {
EXPECT_EQ(creds, ret_creds);
}

TEST(CreateCredentialsProviderFromConfig, InlineCredential) {
NiceMock<Server::Configuration::MockServerFactoryContext> context;
envoy::extensions::common::aws::v3::InlineCredentialProvider inline_credential;
inline_credential.set_access_key_id("TestAccessKey");
inline_credential.set_secret_access_key("TestSecret");
inline_credential.set_session_token("TestSessionToken");

envoy::extensions::common::aws::v3::AwsCredentialProvider base;
base.mutable_inline_credential()->CopyFrom(inline_credential);

CredentialsProviderSharedPtr provider =
createCredentialsProviderFromConfig(context, "test-region", base);
EXPECT_NE(nullptr, provider);
const Credentials creds = provider->getCredentials();
EXPECT_EQ("TestAccessKey", creds.accessKeyId().value());
EXPECT_EQ("TestSecret", creds.secretAccessKey().value());
EXPECT_EQ("TestSessionToken", creds.sessionToken().value());
}

TEST(CreateCredentialsProviderFromConfig, AssumeRoleWithWebIdentity) {
NiceMock<Server::Configuration::MockServerFactoryContext> context;
envoy::extensions::common::aws::v3::AssumeRoleWithWebIdentityCredentialProvider
assume_role_provider;
assume_role_provider.set_role_arn("arn:aws:iam::123456789012:role/role-name");
assume_role_provider.set_web_identity_token("this-is-a-token");

envoy::extensions::common::aws::v3::AwsCredentialProvider base;
base.mutable_assume_role_with_web_identity()->CopyFrom(assume_role_provider);

CredentialsProviderSharedPtr provider =
createCredentialsProviderFromConfig(context, "test-region", base);
EXPECT_NE(nullptr, provider);

const auto* web_identity_provider = dynamic_cast<WebIdentityCredentialsProvider*>(provider.get());
EXPECT_NE(nullptr, web_identity_provider);

const std::string& token = web_identity_provider->tokenForTesting();
const std::string& role_arn = web_identity_provider->roleArnForTesting();
EXPECT_EQ("this-is-a-token", token);
EXPECT_EQ("arn:aws:iam::123456789012:role/role-name", role_arn);
}

} // namespace Aws
} // namespace Common
} // namespace Extensions
Expand Down
74 changes: 74 additions & 0 deletions test/extensions/filters/http/aws_request_signing/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,80 @@ host_rewrite: new-host
cb(filter_callbacks);
}

TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_inline) {
const std::string yaml = R"EOF(
service_name: s3
region: us-west-2
credential_provider:
inline_credential:
access_key_id: access_key
secret_access_key: secret_key
session_token: session_token
)EOF";

AwsRequestSigningProtoConfig proto_config;
TestUtility::loadFromYamlAndValidate(yaml, proto_config);

AwsRequestSigningProtoConfig expected_config;
expected_config.set_service_name("s3");
expected_config.set_region("us-west-2");
auto* credential_provider =
expected_config.mutable_credential_provider()->mutable_inline_credential();
credential_provider->set_access_key_id("access_key");
credential_provider->set_secret_access_key("secret_key");
credential_provider->set_session_token("session_token");

Protobuf::util::MessageDifferencer differencer;
differencer.set_message_field_comparison(Protobuf::util::MessageDifferencer::EQUAL);
differencer.set_repeated_field_comparison(Protobuf::util::MessageDifferencer::AS_SET);
EXPECT_TRUE(differencer.Compare(expected_config, proto_config));

testing::NiceMock<Server::Configuration::MockFactoryContext> context;
AwsRequestSigningFilterFactory factory;

Http::FilterFactoryCb cb =
factory.createFilterFactoryFromProto(proto_config, "stats", context).value();
Http::MockFilterChainFactoryCallbacks filter_callbacks;
EXPECT_CALL(filter_callbacks, addStreamDecoderFilter(_));
cb(filter_callbacks);
}

TEST(AwsRequestSigningFilterConfigTest, CredentialProvider_assume_role_web_identity) {
const std::string yaml = R"EOF(
service_name: s3
region: us-west-2
credential_provider:
assume_role_with_web_identity:
web_identity_token: this-is-token
role_arn: arn:aws:iam::123456789012:role/role-name
)EOF";

AwsRequestSigningProtoConfig proto_config;
TestUtility::loadFromYamlAndValidate(yaml, proto_config);

AwsRequestSigningProtoConfig expected_config;
expected_config.set_service_name("s3");
expected_config.set_region("us-west-2");
auto credential_provider =
expected_config.mutable_credential_provider()->mutable_assume_role_with_web_identity();
credential_provider->set_web_identity_token("this-is-token");
credential_provider->set_role_arn("arn:aws:iam::123456789012:role/role-name");

Protobuf::util::MessageDifferencer differencer;
differencer.set_message_field_comparison(Protobuf::util::MessageDifferencer::EQUAL);
differencer.set_repeated_field_comparison(Protobuf::util::MessageDifferencer::AS_SET);
EXPECT_TRUE(differencer.Compare(expected_config, proto_config));

testing::NiceMock<Server::Configuration::MockFactoryContext> context;
AwsRequestSigningFilterFactory factory;

Http::FilterFactoryCb cb =
factory.createFilterFactoryFromProto(proto_config, "stats", context).value();
Http::MockFilterChainFactoryCallbacks filter_callbacks;
EXPECT_CALL(filter_callbacks, addStreamDecoderFilter(_));
cb(filter_callbacks);
}

TEST(AwsRequestSigningFilterConfigTest, SimpleConfigExplicitSigningAlgorithm) {
const std::string yaml = R"EOF(
service_name: s3
Expand Down

0 comments on commit 53c3873

Please sign in to comment.