Added url validation for aws metadata endpoints in aws external account (#31626)

* Added url validation for aws metadata endpoints in aws external account

* addressing review comments

* fix error message back

* Fix broken test
pull/31635/head
aeitzman 2 years ago committed by GitHub
parent 571e98f6d5
commit d934aabb09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      src/core/lib/security/credentials/external/aws_external_account_credentials.cc
  2. 195
      test/core/security/credentials_test.cc
  3. 5
      test/cpp/client/credentials_test.cc

@ -37,6 +37,7 @@
#include <grpc/support/string_util.h>
#include "src/core/lib/gprpp/env.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/http/httpcli_ssl_credentials.h"
#include "src/core/lib/iomgr/closure.h"
#include "src/core/lib/json/json.h"
@ -47,6 +48,9 @@ namespace grpc_core {
namespace {
const char* awsEc2MetadataIpv4Address = "169.254.169.254";
const char* awsEc2MetadataIpv6Address = "fd00:ec2::254";
const char* kExpectedEnvironmentId = "aws1";
const char* kRegionEnvVar = "AWS_REGION";
@ -73,6 +77,15 @@ std::string UrlEncode(const absl::string_view& s) {
return result;
}
bool ValidateAwsUrl(const std::string& urlString) {
absl::StatusOr<URI> url = URI::Parse(urlString);
if (!url.ok()) return false;
absl::string_view host;
absl::string_view port;
SplitHostPort(url->authority(), &host, &port);
return host == awsEc2MetadataIpv4Address || host == awsEc2MetadataIpv6Address;
}
} // namespace
RefCountedPtr<AwsExternalAccountCredentials>
@ -115,10 +128,22 @@ AwsExternalAccountCredentials::AwsExternalAccountCredentials(
return;
}
region_url_ = it->second.string_value();
if (!ValidateAwsUrl(region_url_)) {
*error = GRPC_ERROR_CREATE(absl::StrFormat(
"Invalid host for region_url field, expecting %s or %s.",
awsEc2MetadataIpv4Address, awsEc2MetadataIpv6Address));
return;
}
it = options.credential_source.object_value().find("url");
if (it != options.credential_source.object_value().end() &&
it->second.type() == Json::Type::STRING) {
url_ = it->second.string_value();
if (!ValidateAwsUrl(url_)) {
*error = GRPC_ERROR_CREATE(absl::StrFormat(
"Invalid host for url field, expecting %s or %s.",
awsEc2MetadataIpv4Address, awsEc2MetadataIpv6Address));
return;
}
}
it = options.credential_source.object_value().find(
"regional_cred_verification_url");
@ -138,6 +163,13 @@ AwsExternalAccountCredentials::AwsExternalAccountCredentials(
if (it != options.credential_source.object_value().end() &&
it->second.type() == Json::Type::STRING) {
imdsv2_session_token_url_ = it->second.string_value();
if (!ValidateAwsUrl(imdsv2_session_token_url_)) {
*error = GRPC_ERROR_CREATE(absl::StrFormat(
"Invalid host for imdsv2_session_token_url field, expecting %s or "
"%s.",
awsEc2MetadataIpv4Address, awsEc2MetadataIpv6Address));
return;
}
}
}

@ -278,16 +278,25 @@ const char aws_imdsv2_session_token[] = "imdsv2_session_token";
const char valid_aws_external_account_creds_options_credential_source[] =
"{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\","
"\"url\":\"https://foo.com:5555/url\","
"\"region_url\":\"https://169.254.169.254:5555/region_url\","
"\"url\":\"https://169.254.169.254:5555/url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
const char valid_aws_imdsv2_external_account_creds_options_credential_source[] =
"{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\","
"\"url\":\"https://foo.com:5555/url\","
"\"imdsv2_session_token_url\":\"https://foo.com:5555/"
"\"region_url\":\"http://169.254.169.254:5555/region_url\","
"\"url\":\"https://169.254.169.254:5555/url\","
"\"imdsv2_session_token_url\":\"https://169.254.169.254/"
"imdsv2_session_token_url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
const char valid_aws_external_account_creds_options_credential_source_ipv6[] =
"{\"environment_id\":\"aws1\","
"\"region_url\":\"https://[fd00:ec2::254]:5555/region_url\","
"\"url\":\"http://[fd00:ec2::254]:5555/url\","
"\"imdsv2_session_token_url\":\"https://[fd00:ec2::254]/"
"imdsv2_session_token_url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
@ -295,43 +304,53 @@ const char valid_aws_imdsv2_external_account_creds_options_credential_source[] =
const char
invalid_aws_external_account_creds_options_credential_source_unmatched_environment_id
[] = "{\"environment_id\":\"unsupported_aws_version\","
"\"region_url\":\"https://foo.com:5555/region_url\","
"\"url\":\"https://foo.com:5555/url\","
"\"region_url\":\"https://169.254.169.254:5555/region_url\","
"\"url\":\"https://169.254.169.254:5555/url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
const char
invalid_aws_external_account_creds_options_credential_source_invalid_region_url
invalid_aws_external_account_creds_options_credential_source_invalid_region_url_host
[] = "{\"environment_id\":\"aws1\","
"\"region_url\":\"invalid_region_url\","
"\"url\":\"https://foo.com:5555/url\","
"\"region_url\":\"https://fakeurl.com/url\","
"\"url\":\"https://169.254.169.254:5555/url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
const char
invalid_aws_external_account_creds_options_credential_source_invalid_url[] =
"{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\","
"\"url\":\"invalid_url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
invalid_aws_external_account_creds_options_credential_source_invalid_url_host
[] = "{\"environment_id\":\"aws1\","
"\"region_url\":\"https://169.254.169.254:5555/region_url\","
"\"url\":\"https://fake.com/url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
const char
invalid_aws_external_account_creds_options_credential_source_missing_role_name
[] = "{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\","
"\"url\":\"https://foo.com:5555/url_no_role_name\","
"\"region_url\":\"https://169.254.169.254:5555/region_url\","
"\"url\":\"https://169.254.169.254:5555/url_no_role_name\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
const char
invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url
[] = "{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\","
"\"url\":\"https://foo.com:5555/url_no_role_name\","
"\"region_url\":\"https://169.254.169.254:5555/region_url\","
"\"url\":\"https://169.254.169.254:5555/url_no_role_name\","
"\"regional_cred_verification_url\":\"invalid_regional_cred_"
"verification_url\"}";
const char
invalid_aws_external_account_creds_options_credential_source_invalid_imdsv2_url_host
[] = "{\"environment_id\":\"aws1\","
"\"region_url\":\"https://169.254.169.254:5555/region_url\","
"\"url\":\"https://169.254.169.254:5555/url\","
"\"imdsv2_session_token_url\":\"https://foo.com/"
"imdsv2_session_token_url\","
"\"regional_cred_verification_url\":\"https://foo.com:5555/"
"regional_cred_verification_url_{region}\"}";
/* -- Global state flags. -- */
bool g_test_is_on_gce = false;
@ -3030,6 +3049,41 @@ TEST(CredentialsTest, TestAwsImdsv2ExternalAccountCredsSuccess) {
HttpRequest::SetOverride(nullptr, nullptr, nullptr);
}
TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessIpv6) {
ExecCtx exec_ctx;
auto credential_source = Json::Parse(
valid_aws_external_account_creds_options_credential_source_ipv6);
GPR_ASSERT(credential_source.ok());
ExternalAccountCredentials::Options options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"", // service_account_impersonation_url;
"https://foo.com:5555/token", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
*credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
"", // workforce_pool_user_project;
};
grpc_error_handle error;
auto creds = AwsExternalAccountCredentials::Create(options, {}, &error);
GPR_ASSERT(creds != nullptr);
GPR_ASSERT(error.ok());
GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
auto state = RequestMetadataState::NewInstance(
absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
HttpRequest::SetOverride(
aws_imdsv2_external_account_creds_httpcli_get_success,
aws_external_account_creds_httpcli_post_success,
aws_imdsv2_external_account_creds_httpcli_put_success);
state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
kTestPath);
ExecCtx::Get()->Flush();
HttpRequest::SetOverride(nullptr, nullptr, nullptr);
}
TEST(CredentialsTest, TestAwsExternalAccountCredsSuccessPathRegionEnvKeysUrl) {
ExecCtx exec_ctx;
SetEnv("AWS_REGION", "test_regionz");
@ -3344,10 +3398,10 @@ TEST(CredentialsTest,
GPR_ASSERT(expected_error == actual_error);
}
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidRegionUrl) {
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureMissingRoleName) {
ExecCtx exec_ctx;
auto credential_source = Json::Parse(
invalid_aws_external_account_creds_options_credential_source_invalid_region_url);
invalid_aws_external_account_creds_options_credential_source_missing_role_name);
GPR_ASSERT(credential_source.ok());
ExternalAccountCredentials::Options options = {
"external_account", // type;
@ -3367,7 +3421,7 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidRegionUrl) {
GPR_ASSERT(creds != nullptr);
GPR_ASSERT(error.ok());
GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
error = GRPC_ERROR_CREATE("Invalid region url: invalid_region_url.");
error = GRPC_ERROR_CREATE("Missing role name when retrieving signing keys.");
grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING(
"Error occurred when fetching oauth2 token.", &error, 1);
auto state = RequestMetadataState::NewInstance(expected_error, {});
@ -3380,10 +3434,11 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidRegionUrl) {
HttpRequest::SetOverride(nullptr, nullptr, nullptr);
}
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidUrl) {
TEST(CredentialsTest,
TestAwsExternalAccountCredsFailureInvalidRegionalCredVerificationUrl) {
ExecCtx exec_ctx;
auto credential_source = Json::Parse(
invalid_aws_external_account_creds_options_credential_source_invalid_url);
invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url);
GPR_ASSERT(credential_source.ok());
ExternalAccountCredentials::Options options = {
"external_account", // type;
@ -3403,7 +3458,7 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidUrl) {
GPR_ASSERT(creds != nullptr);
GPR_ASSERT(error.ok());
GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
error = GRPC_ERROR_CREATE("Invalid url: invalid_url.");
error = GRPC_ERROR_CREATE("Creating aws request signer failed.");
grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING(
"Error occurred when fetching oauth2 token.", &error, 1);
auto state = RequestMetadataState::NewInstance(expected_error, {});
@ -3416,10 +3471,9 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidUrl) {
HttpRequest::SetOverride(nullptr, nullptr, nullptr);
}
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureMissingRoleName) {
ExecCtx exec_ctx;
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidUrlHost) {
auto credential_source = Json::Parse(
invalid_aws_external_account_creds_options_credential_source_missing_role_name);
invalid_aws_external_account_creds_options_credential_source_invalid_url_host);
GPR_ASSERT(credential_source.ok());
ExternalAccountCredentials::Options options = {
"external_account", // type;
@ -3436,27 +3490,18 @@ TEST(CredentialsTest, TestAwsExternalAccountCredsFailureMissingRoleName) {
};
grpc_error_handle error;
auto creds = AwsExternalAccountCredentials::Create(options, {}, &error);
GPR_ASSERT(creds != nullptr);
GPR_ASSERT(error.ok());
GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
error = GRPC_ERROR_CREATE("Missing role name when retrieving signing keys.");
grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING(
"Error occurred when fetching oauth2 token.", &error, 1);
auto state = RequestMetadataState::NewInstance(expected_error, {});
HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
aws_external_account_creds_httpcli_post_success,
httpcli_put_should_not_be_called);
state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
kTestPath);
ExecCtx::Get()->Flush();
HttpRequest::SetOverride(nullptr, nullptr, nullptr);
GPR_ASSERT(creds == nullptr);
std::string expected_error =
"Invalid host for url field, expecting 169.254.169.254 or fd00:ec2::254.";
std::string actual_error;
GPR_ASSERT(grpc_error_get_str(error, StatusStrProperty::kDescription,
&actual_error));
GPR_ASSERT(expected_error == actual_error);
}
TEST(CredentialsTest,
TestAwsExternalAccountCredsFailureInvalidRegionalCredVerificationUrl) {
ExecCtx exec_ctx;
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidRegionUrlHost) {
auto credential_source = Json::Parse(
invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url);
invalid_aws_external_account_creds_options_credential_source_invalid_region_url_host);
GPR_ASSERT(credential_source.ok());
ExternalAccountCredentials::Options options = {
"external_account", // type;
@ -3473,20 +3518,43 @@ TEST(CredentialsTest,
};
grpc_error_handle error;
auto creds = AwsExternalAccountCredentials::Create(options, {}, &error);
GPR_ASSERT(creds != nullptr);
GPR_ASSERT(error.ok());
GPR_ASSERT(creds->min_security_level() == GRPC_PRIVACY_AND_INTEGRITY);
error = GRPC_ERROR_CREATE("Creating aws request signer failed.");
grpc_error_handle expected_error = GRPC_ERROR_CREATE_REFERENCING(
"Error occurred when fetching oauth2 token.", &error, 1);
auto state = RequestMetadataState::NewInstance(expected_error, {});
HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
aws_external_account_creds_httpcli_post_success,
httpcli_put_should_not_be_called);
state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
kTestPath);
ExecCtx::Get()->Flush();
HttpRequest::SetOverride(nullptr, nullptr, nullptr);
GPR_ASSERT(creds == nullptr);
std::string expected_error =
"Invalid host for region_url field, expecting 169.254.169.254 or "
"fd00:ec2::254.";
std::string actual_error;
GPR_ASSERT(grpc_error_get_str(error, StatusStrProperty::kDescription,
&actual_error));
GPR_ASSERT(expected_error == actual_error);
}
TEST(CredentialsTest, TestAwsExternalAccountCredsFailureInvalidImdsv2UrlHost) {
auto credential_source = Json::Parse(
invalid_aws_external_account_creds_options_credential_source_invalid_imdsv2_url_host);
GPR_ASSERT(credential_source.ok());
ExternalAccountCredentials::Options options = {
"external_account", // type;
"audience", // audience;
"subject_token_type", // subject_token_type;
"", // service_account_impersonation_url;
"https://foo.com:5555/token", // token_url;
"https://foo.com:5555/token_info", // token_info_url;
*credential_source, // credential_source;
"quota_project_id", // quota_project_id;
"client_id", // client_id;
"client_secret", // client_secret;
"", // workforce_pool_user_project;
};
grpc_error_handle error;
auto creds = AwsExternalAccountCredentials::Create(options, {}, &error);
GPR_ASSERT(creds == nullptr);
std::string expected_error =
"Invalid host for imdsv2_session_token_url field, expecting "
"169.254.169.254 or fd00:ec2::254.";
std::string actual_error;
GPR_ASSERT(grpc_error_get_str(error, StatusStrProperty::kDescription,
&actual_error));
GPR_ASSERT(expected_error == actual_error);
}
TEST(CredentialsTest, TestExternalAccountCredentialsCreateSuccess) {
@ -3529,8 +3597,9 @@ TEST(CredentialsTest, TestExternalAccountCredentialsCreateSuccess) {
"url\":\"service_account_impersonation_url\",\"token_url\":\"https://"
"foo.com:5555/token\",\"token_info_url\":\"https://foo.com:5555/"
"token_info\",\"credential_source\":{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\",\"url\":\"https://"
"foo.com:5555/url\",\"regional_cred_verification_url\":\"https://"
"\"region_url\":\"https://169.254.169.254:5555/"
"region_url\",\"url\":\"https://"
"169.254.169.254:5555/url\",\"regional_cred_verification_url\":\"https://"
"foo.com:5555/regional_cred_verification_url_{region}\"},"
"\"quota_project_id\":\"quota_"
"project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"

@ -102,8 +102,9 @@ TEST(CredentialsTest, ExternalAccountCredentials) {
"url\":\"service_account_impersonation_url\",\"token_url\":\"https://"
"foo.com:5555/token\",\"token_info_url\":\"https://foo.com:5555/"
"token_info\",\"credential_source\":{\"environment_id\":\"aws1\","
"\"region_url\":\"https://foo.com:5555/region_url\",\"url\":\"https://"
"foo.com:5555/url\",\"regional_cred_verification_url\":\"https://"
"\"region_url\":\"https://169.254.169.254:5555/"
"region_url\",\"url\":\"https://"
"169.254.169.254:5555/url\",\"regional_cred_verification_url\":\"https://"
"foo.com:5555/regional_cred_verification_url_{region}\"},"
"\"quota_project_id\":\"quota_"
"project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"

Loading…
Cancel
Save