Skip to content

Commit

Permalink
impl(oauth2): validate some URLs in AWS external account (#10448)
Browse files Browse the repository at this point in the history
The spec (aip.dev/auth/4117) requires validation for some URL fields in
the AWS external account configuration.
  • Loading branch information
coryan committed Dec 15, 2022
1 parent 9fa883e commit 432696a
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 23 deletions.
34 changes: 31 additions & 3 deletions google/cloud/internal/external_account_token_source_aws.cc
Original file line number Diff line number Diff line change
Expand Up @@ -100,19 +100,47 @@ StatusOr<ExternalAccountTokenSourceAwsInfo> ParseExternalAccountTokenSourceAws(
auto url = ValidateStringField(credentials_source, "url",
"credentials-source", kDefaultUrl, ec);
if (!url) return std::move(url).status();
auto regional_url =
auto verification_url =
ValidateStringField(credentials_source, "regional_cred_verification_url",
"credentials-source", ec);
if (!regional_url) return std::move(regional_url).status();
if (!verification_url) return std::move(verification_url).status();
auto imdsv2 =
ValidateStringField(credentials_source, "imdsv2_session_token_url",
"credentials-source", std::string{}, ec);
if (!imdsv2) return std::move(imdsv2).status();

auto invalid_url = [](absl::string_view name, absl::string_view value) {
return absl::StrCat(
"the `", name,
"` field should refer to the AWS metadata service, got=<", value, ">");
};
auto targets_metadata = [](absl::string_view url) {
// We probably need a full URL parser to verify the host part is either
// `169.254.169.254` or `fd00:ec2::254`. We just assume there is no
// `userinfo` component. The AWS documentation makes no reference to it, and
// the component is deprecated in any case.
return absl::StartsWith(url, "http://169.254.169.254") ||
absl::StartsWith(url, "http://[fd00:ec2::254]");
};
if (!targets_metadata(*url)) {
return InvalidArgumentError(invalid_url("url", *url),
GCP_ERROR_INFO().WithContext(ec));
}
if (!targets_metadata(*region_url)) {
return InvalidArgumentError(invalid_url("region_url", *region_url),
GCP_ERROR_INFO().WithContext(ec));
}
if (!imdsv2->empty() && !targets_metadata(*imdsv2)) {
return InvalidArgumentError(
invalid_url("imdsv2_session_token_url", *imdsv2),
GCP_ERROR_INFO().WithContext(ec));
}

return ExternalAccountTokenSourceAwsInfo{
/*environment_id=*/*std::move(environment_id),
/*region_url=*/*std::move(region_url),
/*url=*/*std::move(url),
/*regional_cred_verification_url=*/*std::move(regional_url),
/*regional_cred_verification_url=*/*std::move(verification_url),
/*imdsv2_session_token_url=*/*std::move(imdsv2)};
}

Expand Down
116 changes: 96 additions & 20 deletions google/cloud/internal/external_account_token_source_aws_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,55 +103,77 @@ ExternalAccountTokenSourceAwsInfo MakeTestInfoImdsV2() {
TEST(ExternalAccountTokenSource, ParseSuccess) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", "test-region-url"},
{"url", "test-url"},
{"regional_cred_verification_url", "test-verification-url"},
{"imdsv2_session_token_url", "test-imdsv2"},
{"region_url", kTestRegionUrl},
{"url", kTestMetadataUrl},
{"regional_cred_verification_url", kTestVerificationUrl},
{"imdsv2_session_token_url", kTestImdsv2Url},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
ASSERT_STATUS_OK(info);
EXPECT_EQ(info->environment_id, "aws1");
EXPECT_EQ(info->region_url, kTestRegionUrl);
EXPECT_EQ(info->url, kTestMetadataUrl);
EXPECT_EQ(info->regional_cred_verification_url, kTestVerificationUrl);
EXPECT_EQ(info->imdsv2_session_token_url, kTestImdsv2Url);
}

TEST(ExternalAccountTokenSource, ParseSuccessIPv6) {
auto constexpr kTestMetadataUrlV6 = "http://[fd00:ec2::254]/metadata";
auto constexpr kTestRegionUrlV6 = "http://[fd00:ec2::254]/region";
auto constexpr kTestImdsv2UrlV6 = "http://[fd00:ec2::254]/imdsv2";

auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", kTestRegionUrlV6},
{"url", kTestMetadataUrlV6},
{"regional_cred_verification_url", kTestVerificationUrl},
{"imdsv2_session_token_url", kTestImdsv2UrlV6},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
ASSERT_STATUS_OK(info);
EXPECT_EQ(info->environment_id, "aws1");
EXPECT_EQ(info->region_url, "test-region-url");
EXPECT_EQ(info->url, "test-url");
EXPECT_EQ(info->regional_cred_verification_url, "test-verification-url");
EXPECT_EQ(info->imdsv2_session_token_url, "test-imdsv2");
EXPECT_EQ(info->region_url, kTestRegionUrlV6);
EXPECT_EQ(info->url, kTestMetadataUrlV6);
EXPECT_EQ(info->regional_cred_verification_url, kTestVerificationUrl);
EXPECT_EQ(info->imdsv2_session_token_url, kTestImdsv2UrlV6);
}

TEST(ExternalAccountTokenSource, ParseSuccessNoUrl) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", "test-region-url"},
{"region_url", kTestRegionUrl},
// {"url", "test-url"},
{"regional_cred_verification_url", "test-verification-url"},
{"imdsv2_session_token_url", "test-imdsv2"},
{"regional_cred_verification_url", kTestVerificationUrl},
{"imdsv2_session_token_url", kTestImdsv2Url},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
ASSERT_STATUS_OK(info);
EXPECT_EQ(info->environment_id, "aws1");
EXPECT_EQ(info->region_url, "test-region-url");
EXPECT_EQ(info->region_url, kTestRegionUrl);
EXPECT_EQ(info->url,
"http://169.254.169.254/latest/meta-data/iam/security-credentials");
EXPECT_EQ(info->regional_cred_verification_url, "test-verification-url");
EXPECT_EQ(info->imdsv2_session_token_url, "test-imdsv2");
EXPECT_EQ(info->regional_cred_verification_url, kTestVerificationUrl);
EXPECT_EQ(info->imdsv2_session_token_url, kTestImdsv2Url);
}

TEST(ExternalAccountTokenSource, ParseSuccessNoImdsv2) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", "test-region-url"},
{"url", "test-url"},
{"regional_cred_verification_url", "test-verification-url"},
{"region_url", kTestRegionUrl},
{"url", kTestMetadataUrl},
{"regional_cred_verification_url", kTestVerificationUrl},
// {"imdsv2_session_token_url", "test-imdsv2"},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
ASSERT_STATUS_OK(info);
EXPECT_EQ(info->environment_id, "aws1");
EXPECT_EQ(info->region_url, "test-region-url");
EXPECT_EQ(info->url, "test-url");
EXPECT_EQ(info->regional_cred_verification_url, "test-verification-url");
EXPECT_EQ(info->region_url, kTestRegionUrl);
EXPECT_EQ(info->url, kTestMetadataUrl);
EXPECT_EQ(info->regional_cred_verification_url, kTestVerificationUrl);
EXPECT_THAT(info->imdsv2_session_token_url, IsEmpty());
}

Expand Down Expand Up @@ -248,6 +270,23 @@ TEST(ExternalAccountTokenSource, InvalidRegionUrl) {
Pair("key", "value")}));
}

TEST(ExternalAccountTokenSource, NonMetadataRegionUrl) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", "https://example.com"},
{"regional_cred_verification_url", "test-verification-url"},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
EXPECT_THAT(info,
StatusIs(StatusCode::kInvalidArgument,
AllOf(HasSubstr("the `region_url` field should refer"),
HasSubstr("https://example.com"))));
EXPECT_THAT(info.status().error_info().metadata(),
IsSupersetOf({Pair("filename", "my-credentials.json"),
Pair("key", "value")}));
}

TEST(ExternalAccountTokenSource, InvalidUrl) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
Expand All @@ -264,6 +303,23 @@ TEST(ExternalAccountTokenSource, InvalidUrl) {
Pair("key", "value")}));
}

TEST(ExternalAccountTokenSource, NonMetadataUrl) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", "http://169.254.169.254/region"},
{"url", "https://example.com"},
{"regional_cred_verification_url", "test-verification-url"},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
EXPECT_THAT(info, StatusIs(StatusCode::kInvalidArgument,
AllOf(HasSubstr("the `url` field should refer"),
HasSubstr("https://example.com"))));
EXPECT_THAT(info.status().error_info().metadata(),
IsSupersetOf({Pair("filename", "my-credentials.json"),
Pair("key", "value")}));
}

TEST(ExternalAccountTokenSource, MissingRegionalCredentialVerificationUrl) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"}, {"region_url", "test-region-url"},
Expand Down Expand Up @@ -317,6 +373,26 @@ TEST(ExternalAccountTokenSource, InvalidImdsv2SessionTokenUrl) {
Pair("key", "value")}));
}

TEST(ExternalAccountTokenSource, NonMetadataImdsv2SessionTokenUrl) {
auto const creds = nlohmann::json{
{"environment_id", "aws1"},
{"region_url", kTestRegionUrl},
{"regional_cred_verification_url", kTestVerificationUrl},
{"imdsv2_session_token_url", "https://example.com"},
};
auto const info =
ParseExternalAccountTokenSourceAws(creds, MakeTestErrorContext());
EXPECT_THAT(
info,
StatusIs(
StatusCode::kInvalidArgument,
AllOf(HasSubstr("the `imdsv2_session_token_url` field should refer"),
HasSubstr("https://example.com"))));
EXPECT_THAT(info.status().error_info().metadata(),
IsSupersetOf({Pair("filename", "my-credentials.json"),
Pair("key", "value")}));
}

TEST(ExternalAccountTokenSource, FetchMetadataTokenV1) {
auto const info = MakeTestInfoImdsV1();

Expand Down

0 comments on commit 432696a

Please sign in to comment.