Skip to content

Commit 1ea2cd1

Browse files
committed
use pool not project in workforce identity federation
1 parent c7814f6 commit 1ea2cd1

3 files changed

Lines changed: 72 additions & 44 deletions

File tree

google/cloud/internal/oauth2_external_account_credentials.cc

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,39 @@ StatusOr<ExternalAccountTokenSource> MakeExternalAccountTokenSource(
5050
GCP_ERROR_INFO().WithContext(ec));
5151
}
5252

53-
absl::optional<WorkloadIdentityFederationInfo> WorkloadIdentityFromAudience(
54-
std::string const& audience) {
55-
auto constexpr kPattern =
53+
std::variant<std::monostate, WorkforceIdentityFederationInfo,
54+
WorkloadIdentityFederationInfo>
55+
IdentityFederationFromAudience(std::string const& audience) {
56+
auto constexpr kWorkloadPattern =
5657
R"""(iam.googleapis.com/projects/([^/]+)/locations/global/workloadIdentityPools/([^/]+)/)""";
57-
static auto* re = new std::regex{kPattern, std::regex::optimize};
58+
static auto* workload_re =
59+
new std::regex{kWorkloadPattern, std::regex::optimize};
60+
61+
auto constexpr kWorkforcePattern =
62+
R"""(iam.googleapis.com/locations/global/workforcePools/([^/]+)/)""";
63+
static auto* workforce_re =
64+
new std::regex{kWorkforcePattern, std::regex::optimize};
65+
5866
std::smatch match;
59-
if (!std::regex_search(audience, match, *re)) {
60-
return absl::nullopt;
67+
if (std::regex_search(audience, match, *workload_re)) {
68+
return WorkloadIdentityFederationInfo{match[1], match[2]};
6169
}
62-
return WorkloadIdentityFederationInfo{match[1], match[2]};
70+
if (std::regex_search(audience, match, *workforce_re)) {
71+
return WorkforceIdentityFederationInfo{match[1]};
72+
}
73+
return std::monostate{};
6374
}
6475

6576
} // namespace
6677

6778
bool ExternalAccountInfo::IsWorkforceIdentityFederation() const {
68-
return workforce_pool_user_project.has_value();
79+
return std::holds_alternative<WorkforceIdentityFederationInfo>(
80+
identity_federation_info);
6981
}
7082

7183
bool ExternalAccountInfo::IsWorkloadIdentityFederation() const {
72-
return workload_info.has_value();
84+
return std::holds_alternative<WorkloadIdentityFederationInfo>(
85+
identity_federation_info);
7386
}
7487

7588
/// Parse a JSON string with an external account configuration.
@@ -91,9 +104,7 @@ StatusOr<ExternalAccountInfo> ParseExternalAccountConfiguration(
91104

92105
auto audience = ValidateStringField(json, "audience", "credentials-file", ec);
93106
if (!audience) return std::move(audience).status();
94-
95-
// extract workload project_number and pool_id from audience, if it exists
96-
auto workload_identity = WorkloadIdentityFromAudience(*audience);
107+
auto identity_federation = IdentityFederationFromAudience(*audience);
97108

98109
auto subject_token_type =
99110
ValidateStringField(json, "subject_token_type", "credentials-file", ec);
@@ -134,7 +145,7 @@ StatusOr<ExternalAccountInfo> ParseExternalAccountConfiguration(
134145
absl::nullopt,
135146
*std::move(universe_domain),
136147
std::move(workforce_pool_user_project),
137-
std::move(workload_identity)};
148+
std::move(identity_federation)};
138149

139150
it = json.find("service_account_impersonation_url");
140151
if (it == json.end()) return info;
@@ -253,11 +264,14 @@ ExternalAccountCredentials::AllowedLocationsRequest() const {
253264
// TODO(#16079): Remove conditional and else clause when GA.
254265
#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB
255266
if (info_.IsWorkforceIdentityFederation()) {
256-
request = WorkforceIdentityAllowedLocationsRequest{
257-
*info_.workforce_pool_user_project};
267+
auto wif = std::get<WorkforceIdentityFederationInfo>(
268+
info_.identity_federation_info);
269+
request = WorkforceIdentityAllowedLocationsRequest{wif.pool_id};
258270
} else if (info_.IsWorkloadIdentityFederation()) {
259-
request = WorkloadIdentityAllowedLocationsRequest{
260-
info_.workload_info->project_id, info_.workload_info->pool_id};
271+
auto wif = std::get<WorkloadIdentityFederationInfo>(
272+
info_.identity_federation_info);
273+
request =
274+
WorkloadIdentityAllowedLocationsRequest{wif.project_id, wif.pool_id};
261275
}
262276
#endif
263277
return request;

google/cloud/internal/oauth2_external_account_credentials.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ struct ExternalAccountImpersonationConfig {
5555
std::chrono::seconds token_lifetime;
5656
};
5757

58+
struct WorkforceIdentityFederationInfo {
59+
std::string pool_id;
60+
};
61+
5862
struct WorkloadIdentityFederationInfo {
5963
std::string project_id;
6064
std::string pool_id;
@@ -74,7 +78,9 @@ struct ExternalAccountInfo {
7478
absl::optional<ExternalAccountImpersonationConfig> impersonation_config;
7579
std::string universe_domain;
7680
absl::optional<std::string> workforce_pool_user_project;
77-
absl::optional<WorkloadIdentityFederationInfo> workload_info;
81+
std::variant<std::monostate, WorkforceIdentityFederationInfo,
82+
WorkloadIdentityFederationInfo>
83+
identity_federation_info;
7884
bool IsWorkforceIdentityFederation() const;
7985
bool IsWorkloadIdentityFederation() const;
8086
};

google/cloud/internal/oauth2_external_account_credentials_test.cc

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,9 @@ TEST(ExternalAccount, ParseAwsSuccess) {
190190
EXPECT_EQ(actual->subject_token_type, kTestTokenType);
191191
EXPECT_EQ(actual->token_url, "test-token-url");
192192
EXPECT_EQ(actual->universe_domain, kUniverseDomain);
193-
EXPECT_THAT(actual->workload_info,
194-
Optional(WorkloadIdentityIs("$PROJECT_NUMBER", "$POOL_ID")));
193+
EXPECT_THAT(actual->identity_federation_info,
194+
VariantWith<WorkloadIdentityFederationInfo>(
195+
WorkloadIdentityIs("$PROJECT_NUMBER", "$POOL_ID")));
195196

196197
MockClientFactory client_factory;
197198
EXPECT_CALL(client_factory, Call).Times(0);
@@ -317,10 +318,13 @@ TEST(ExternalAccount, ParseWithImpersonationDefaultLifetimeSuccess) {
317318
std::chrono::seconds(3600));
318319
}
319320

320-
TEST(ExternalAccount, ParseUserProjectSuccess) {
321+
TEST(ExternalAccount, ParseWorkforceIdentityFederationSuccess) {
322+
auto constexpr kWorkforceAudience =
323+
"//iam.googleapis.com/locations/global/workforcePools/$POOL_ID/providers/"
324+
"PROVIDER_ID";
321325
auto const configuration = nlohmann::json{
322326
{"type", "external_account"},
323-
{"audience", "test-audience"},
327+
{"audience", kWorkforceAudience},
324328
{"subject_token_type", "test-subject-token-type"},
325329
{"token_url", "test-token-url"},
326330
{"credential_source", nlohmann::json{{"file", "/dev/null-test-only"}}},
@@ -331,11 +335,14 @@ TEST(ExternalAccount, ParseUserProjectSuccess) {
331335
auto const actual =
332336
ParseExternalAccountConfiguration(configuration.dump(), ec);
333337
ASSERT_STATUS_OK(actual);
334-
EXPECT_EQ(actual->audience, "test-audience");
338+
EXPECT_EQ(actual->audience, kWorkforceAudience);
335339
EXPECT_EQ(actual->subject_token_type, "test-subject-token-type");
336340
EXPECT_EQ(actual->token_url, "test-token-url");
337341
EXPECT_THAT(actual->workforce_pool_user_project,
338342
Optional(std::string("project-id-or-name")));
343+
EXPECT_THAT(actual->identity_federation_info,
344+
VariantWith<WorkforceIdentityFederationInfo>(
345+
WorkforceIdentityIs("$POOL_ID")));
339346
}
340347

341348
TEST(ExternalAccount, ParseNotJson) {
@@ -755,14 +762,15 @@ TEST(ExternalAccount, WorkingWorkforceIdentity) {
755762
auto mock_source = [](HttpClientFactory const&, Options const&) {
756763
return make_status_or(internal::SubjectToken{"test-subject-token"});
757764
};
758-
auto const info = ExternalAccountInfo{"test-audience",
759-
"test-subject-token-type",
760-
test_url,
761-
mock_source,
762-
absl::nullopt,
763-
{},
764-
"project-id-or-name",
765-
absl::nullopt};
765+
auto const info =
766+
ExternalAccountInfo{"test-audience",
767+
"test-subject-token-type",
768+
test_url,
769+
mock_source,
770+
absl::nullopt,
771+
{},
772+
"project-id-or-name",
773+
WorkforceIdentityFederationInfo{"$POOL_ID"}};
766774

767775
MockClientFactory client_factory;
768776
EXPECT_CALL(client_factory, Call(make_expected_options())).WillOnce([&]() {
@@ -797,7 +805,7 @@ TEST(ExternalAccount, WorkingWorkforceIdentity) {
797805
#ifdef GOOGLE_CLOUD_CPP_TESTING_ENABLE_RAB
798806
EXPECT_THAT(credentials.AllowedLocationsRequest(),
799807
VariantWith<WorkforceIdentityAllowedLocationsRequest>(
800-
WorkforceIdentityIs("project-id-or-name")));
808+
WorkforceIdentityIs("$POOL_ID")));
801809
#else
802810
EXPECT_THAT(credentials.AllowedLocationsRequest(),
803811
VariantWith<std::monostate>(std::monostate{}));
@@ -844,7 +852,7 @@ TEST(ExternalAccount, WorkingWithImpersonation) {
844852
impersonate_test_url, impersonate_test_lifetime},
845853
{},
846854
absl::nullopt,
847-
absl::nullopt};
855+
std::monostate{}};
848856

849857
auto sts_client = [&] {
850858
auto expected_sts_request = Property(&RestRequest::path, sts_test_url);
@@ -916,7 +924,7 @@ TEST(ExternalAccount, HandleHttpError) {
916924
ExternalAccountInfo{"test-audience", "test-subject-token-type",
917925
test_url, mock_source,
918926
absl::nullopt, {},
919-
absl::nullopt, absl::nullopt};
927+
absl::nullopt, std::monostate{}};
920928
MockClientFactory client_factory;
921929
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
922930
auto mock = std::make_unique<MockRestClient>();
@@ -955,7 +963,7 @@ TEST(ExternalAccount, HandleHttpPartialError) {
955963
ExternalAccountInfo{"test-audience", "test-subject-token-type",
956964
test_url, mock_source,
957965
absl::nullopt, {},
958-
absl::nullopt, absl::nullopt};
966+
absl::nullopt, std::monostate{}};
959967
MockClientFactory client_factory;
960968
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
961969
auto mock = std::make_unique<MockRestClient>();
@@ -995,7 +1003,7 @@ TEST(ExternalAccount, HandleNotJson) {
9951003
ExternalAccountInfo{"test-audience", "test-subject-token-type",
9961004
test_url, mock_source,
9971005
absl::nullopt, {},
998-
absl::nullopt, absl::nullopt};
1006+
absl::nullopt, std::monostate{}};
9991007
MockClientFactory client_factory;
10001008
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
10011009
auto mock = std::make_unique<MockRestClient>();
@@ -1035,7 +1043,7 @@ TEST(ExternalAccount, HandleNotJsonObject) {
10351043
ExternalAccountInfo{"test-audience", "test-subject-token-type",
10361044
test_url, mock_source,
10371045
absl::nullopt, {},
1038-
absl::nullopt, absl::nullopt};
1046+
absl::nullopt, std::monostate{}};
10391047
MockClientFactory client_factory;
10401048
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
10411049
auto mock = std::make_unique<MockRestClient>();
@@ -1081,7 +1089,7 @@ TEST(ExternalAccount, MissingToken) {
10811089
ExternalAccountInfo{"test-audience", "test-subject-token-type",
10821090
test_url, mock_source,
10831091
absl::nullopt, {},
1084-
absl::nullopt, absl::nullopt};
1092+
absl::nullopt, std::monostate{}};
10851093
MockClientFactory client_factory;
10861094
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
10871095
auto mock = std::make_unique<MockRestClient>();
@@ -1116,7 +1124,7 @@ TEST(ExternalAccount, MissingIssuedTokenType) {
11161124
ExternalAccountInfo{"test-audience", "test-subject-token-type",
11171125
test_url, mock_source,
11181126
absl::nullopt, {},
1119-
absl::nullopt, absl::nullopt};
1127+
absl::nullopt, std::monostate{}};
11201128
MockClientFactory client_factory;
11211129
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
11221130
auto mock = std::make_unique<MockRestClient>();
@@ -1151,7 +1159,7 @@ TEST(ExternalAccount, MissingTokenType) {
11511159
ExternalAccountInfo{"test-audience", "test-subject-token-type",
11521160
test_url, mock_source,
11531161
absl::nullopt, {},
1154-
absl::nullopt, absl::nullopt};
1162+
absl::nullopt, std::monostate{}};
11551163
MockClientFactory client_factory;
11561164
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
11571165
auto mock = std::make_unique<MockRestClient>();
@@ -1186,7 +1194,7 @@ TEST(ExternalAccount, InvalidIssuedTokenType) {
11861194
ExternalAccountInfo{"test-audience", "test-subject-token-type",
11871195
test_url, mock_source,
11881196
absl::nullopt, {},
1189-
absl::nullopt, absl::nullopt};
1197+
absl::nullopt, std::monostate{}};
11901198
MockClientFactory client_factory;
11911199
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
11921200
auto mock = std::make_unique<MockRestClient>();
@@ -1223,7 +1231,7 @@ TEST(ExternalAccount, InvalidTokenType) {
12231231
ExternalAccountInfo{"test-audience", "test-subject-token-type",
12241232
test_url, mock_source,
12251233
absl::nullopt, {},
1226-
absl::nullopt, absl::nullopt};
1234+
absl::nullopt, std::monostate{}};
12271235
MockClientFactory client_factory;
12281236
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
12291237
auto mock = std::make_unique<MockRestClient>();
@@ -1261,7 +1269,7 @@ TEST(ExternalAccount, MissingExpiresIn) {
12611269
ExternalAccountInfo{"test-audience", "test-subject-token-type",
12621270
test_url, mock_source,
12631271
absl::nullopt, {},
1264-
absl::nullopt, absl::nullopt};
1272+
absl::nullopt, std::monostate{}};
12651273
MockClientFactory client_factory;
12661274
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
12671275
auto mock = std::make_unique<MockRestClient>();
@@ -1297,7 +1305,7 @@ TEST(ExternalAccount, InvalidExpiresIn) {
12971305
ExternalAccountInfo{"test-audience", "test-subject-token-type",
12981306
test_url, mock_source,
12991307
absl::nullopt, {},
1300-
absl::nullopt, absl::nullopt};
1308+
absl::nullopt, std::monostate{}};
13011309
MockClientFactory client_factory;
13021310
EXPECT_CALL(client_factory, Call).WillOnce([&]() {
13031311
auto mock = std::make_unique<MockRestClient>();

0 commit comments

Comments
 (0)