1 // Copyright 2020 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15
16 #include <grpc/support/port_platform.h>
17
18 #include "src/core/lib/security/credentials/external/external_account_credentials.h"
19
20 #include <stdint.h>
21 #include <string.h>
22
23 #include <map>
24 #include <memory>
25 #include <utility>
26
27 #include "absl/status/status.h"
28 #include "absl/status/statusor.h"
29 #include "absl/strings/escaping.h"
30 #include "absl/strings/match.h"
31 #include "absl/strings/numbers.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/str_format.h"
34 #include "absl/strings/str_join.h"
35 #include "absl/strings/str_split.h"
36 #include "absl/strings/strip.h"
37 #include "absl/time/clock.h"
38 #include "absl/time/time.h"
39
40 #include <grpc/grpc.h>
41 #include <grpc/grpc_security.h>
42 #include <grpc/support/alloc.h>
43 #include <grpc/support/json.h>
44 #include <grpc/support/log.h>
45 #include <grpc/support/string_util.h>
46
47 #include "src/core/lib/gprpp/status_helper.h"
48 #include "src/core/lib/http/httpcli_ssl_credentials.h"
49 #include "src/core/lib/http/parser.h"
50 #include "src/core/lib/json/json_reader.h"
51 #include "src/core/lib/json/json_writer.h"
52 #include "src/core/lib/security/credentials/credentials.h"
53 #include "src/core/lib/security/credentials/external/aws_external_account_credentials.h"
54 #include "src/core/lib/security/credentials/external/file_external_account_credentials.h"
55 #include "src/core/lib/security/credentials/external/url_external_account_credentials.h"
56 #include "src/core/lib/security/util/json_util.h"
57 #include "src/core/lib/uri/uri_parser.h"
58
59 #define EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE \
60 "urn:ietf:params:oauth:grant-type:token-exchange"
61 #define EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE \
62 "urn:ietf:params:oauth:token-type:access_token"
63 #define GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE \
64 "https://www.googleapis.com/auth/cloud-platform"
65 #define IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS 3600 // 1 hour
66 #define IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS 600 // 10 mins
67 #define IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS 43200 // 12 hours
68
69 namespace grpc_core {
70
71 namespace {
72
UrlEncode(const absl::string_view & s)73 std::string UrlEncode(const absl::string_view& s) {
74 const char* hex = "0123456789ABCDEF";
75 std::string result;
76 result.reserve(s.length());
77 for (auto c : s) {
78 if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
79 (c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '!' ||
80 c == '\'' || c == '(' || c == ')' || c == '*' || c == '~' || c == '.') {
81 result.push_back(c);
82 } else {
83 result.push_back('%');
84 result.push_back(hex[static_cast<unsigned char>(c) >> 4]);
85 result.push_back(hex[static_cast<unsigned char>(c) & 15]);
86 }
87 }
88 return result;
89 }
90
91 // Expression to match:
92 // //iam.googleapis.com/locations/[^/]+/workforcePools/[^/]+/providers/.+
MatchWorkforcePoolAudience(absl::string_view audience)93 bool MatchWorkforcePoolAudience(absl::string_view audience) {
94 // Match "//iam.googleapis.com/locations/"
95 if (!absl::ConsumePrefix(&audience, "//iam.googleapis.com")) return false;
96 if (!absl::ConsumePrefix(&audience, "/locations/")) return false;
97 // Match "[^/]+/workforcePools/"
98 std::pair<absl::string_view, absl::string_view> workforce_pools_split_result =
99 absl::StrSplit(audience, absl::MaxSplits("/workforcePools/", 1));
100 if (absl::StrContains(workforce_pools_split_result.first, '/')) return false;
101 // Match "[^/]+/providers/.+"
102 std::pair<absl::string_view, absl::string_view> providers_split_result =
103 absl::StrSplit(workforce_pools_split_result.second,
104 absl::MaxSplits("/providers/", 1));
105 return !absl::StrContains(providers_split_result.first, '/');
106 }
107
108 } // namespace
109
Create(const Json & json,std::vector<std::string> scopes,grpc_error_handle * error)110 RefCountedPtr<ExternalAccountCredentials> ExternalAccountCredentials::Create(
111 const Json& json, std::vector<std::string> scopes,
112 grpc_error_handle* error) {
113 GPR_ASSERT(error->ok());
114 Options options;
115 options.type = GRPC_AUTH_JSON_TYPE_INVALID;
116 if (json.type() != Json::Type::kObject) {
117 *error =
118 GRPC_ERROR_CREATE("Invalid json to construct credentials options.");
119 return nullptr;
120 }
121 auto it = json.object().find("type");
122 if (it == json.object().end()) {
123 *error = GRPC_ERROR_CREATE("type field not present.");
124 return nullptr;
125 }
126 if (it->second.type() != Json::Type::kString) {
127 *error = GRPC_ERROR_CREATE("type field must be a string.");
128 return nullptr;
129 }
130 if (it->second.string() != GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT) {
131 *error = GRPC_ERROR_CREATE("Invalid credentials json type.");
132 return nullptr;
133 }
134 options.type = GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT;
135 it = json.object().find("audience");
136 if (it == json.object().end()) {
137 *error = GRPC_ERROR_CREATE("audience field not present.");
138 return nullptr;
139 }
140 if (it->second.type() != Json::Type::kString) {
141 *error = GRPC_ERROR_CREATE("audience field must be a string.");
142 return nullptr;
143 }
144 options.audience = it->second.string();
145 it = json.object().find("subject_token_type");
146 if (it == json.object().end()) {
147 *error = GRPC_ERROR_CREATE("subject_token_type field not present.");
148 return nullptr;
149 }
150 if (it->second.type() != Json::Type::kString) {
151 *error = GRPC_ERROR_CREATE("subject_token_type field must be a string.");
152 return nullptr;
153 }
154 options.subject_token_type = it->second.string();
155 it = json.object().find("service_account_impersonation_url");
156 if (it != json.object().end()) {
157 options.service_account_impersonation_url = it->second.string();
158 }
159 it = json.object().find("token_url");
160 if (it == json.object().end()) {
161 *error = GRPC_ERROR_CREATE("token_url field not present.");
162 return nullptr;
163 }
164 if (it->second.type() != Json::Type::kString) {
165 *error = GRPC_ERROR_CREATE("token_url field must be a string.");
166 return nullptr;
167 }
168 options.token_url = it->second.string();
169 it = json.object().find("token_info_url");
170 if (it != json.object().end()) {
171 options.token_info_url = it->second.string();
172 }
173 it = json.object().find("credential_source");
174 if (it == json.object().end()) {
175 *error = GRPC_ERROR_CREATE("credential_source field not present.");
176 return nullptr;
177 }
178 options.credential_source = it->second;
179 it = json.object().find("quota_project_id");
180 if (it != json.object().end()) {
181 options.quota_project_id = it->second.string();
182 }
183 it = json.object().find("client_id");
184 if (it != json.object().end()) {
185 options.client_id = it->second.string();
186 }
187 it = json.object().find("client_secret");
188 if (it != json.object().end()) {
189 options.client_secret = it->second.string();
190 }
191 it = json.object().find("workforce_pool_user_project");
192 if (it != json.object().end()) {
193 if (MatchWorkforcePoolAudience(options.audience)) {
194 options.workforce_pool_user_project = it->second.string();
195 } else {
196 *error = GRPC_ERROR_CREATE(
197 "workforce_pool_user_project should not be set for non-workforce "
198 "pool credentials");
199 return nullptr;
200 }
201 }
202 it = json.object().find("service_account_impersonation");
203 options.service_account_impersonation.token_lifetime_seconds =
204 IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS;
205 if (it != json.object().end() && it->second.type() == Json::Type::kObject) {
206 auto service_acc_imp_json = it->second;
207 auto service_acc_imp_obj_it =
208 service_acc_imp_json.object().find("token_lifetime_seconds");
209 if (service_acc_imp_obj_it != service_acc_imp_json.object().end()) {
210 if (!absl::SimpleAtoi(
211 service_acc_imp_obj_it->second.string(),
212 &options.service_account_impersonation.token_lifetime_seconds)) {
213 *error = GRPC_ERROR_CREATE("token_lifetime_seconds must be a number");
214 return nullptr;
215 }
216 if (options.service_account_impersonation.token_lifetime_seconds >
217 IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS) {
218 *error = GRPC_ERROR_CREATE(
219 absl::StrFormat("token_lifetime_seconds must be less than %ds",
220 IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS));
221 return nullptr;
222 }
223 if (options.service_account_impersonation.token_lifetime_seconds <
224 IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS) {
225 *error = GRPC_ERROR_CREATE(
226 absl::StrFormat("token_lifetime_seconds must be more than %ds",
227 IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS));
228 return nullptr;
229 }
230 }
231 }
232 RefCountedPtr<ExternalAccountCredentials> creds;
233 if (options.credential_source.object().find("environment_id") !=
234 options.credential_source.object().end()) {
235 creds = MakeRefCounted<AwsExternalAccountCredentials>(
236 std::move(options), std::move(scopes), error);
237 } else if (options.credential_source.object().find("file") !=
238 options.credential_source.object().end()) {
239 creds = MakeRefCounted<FileExternalAccountCredentials>(
240 std::move(options), std::move(scopes), error);
241 } else if (options.credential_source.object().find("url") !=
242 options.credential_source.object().end()) {
243 creds = MakeRefCounted<UrlExternalAccountCredentials>(
244 std::move(options), std::move(scopes), error);
245 } else {
246 *error = GRPC_ERROR_CREATE(
247 "Invalid options credential source to create "
248 "ExternalAccountCredentials.");
249 }
250 if (error->ok()) {
251 return creds;
252 } else {
253 return nullptr;
254 }
255 }
256
ExternalAccountCredentials(Options options,std::vector<std::string> scopes)257 ExternalAccountCredentials::ExternalAccountCredentials(
258 Options options, std::vector<std::string> scopes)
259 : options_(std::move(options)) {
260 if (scopes.empty()) {
261 scopes.push_back(GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE);
262 }
263 scopes_ = std::move(scopes);
264 }
265
~ExternalAccountCredentials()266 ExternalAccountCredentials::~ExternalAccountCredentials() {}
267
debug_string()268 std::string ExternalAccountCredentials::debug_string() {
269 return absl::StrFormat("ExternalAccountCredentials{Audience:%s,%s}",
270 options_.audience,
271 grpc_oauth2_token_fetcher_credentials::debug_string());
272 }
273
MetricsHeaderValue()274 std::string ExternalAccountCredentials::MetricsHeaderValue() {
275 return absl::StrFormat(
276 "gl-cpp/unknown auth/%s google-byoid-sdk source/%s sa-impersonation/%v "
277 "config-lifetime/%v",
278 grpc_version_string(), CredentialSourceType(),
279 !options_.service_account_impersonation_url.empty(),
280 options_.service_account_impersonation.token_lifetime_seconds !=
281 IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS);
282 }
283
CredentialSourceType()284 absl::string_view ExternalAccountCredentials::CredentialSourceType() {
285 return "unknown";
286 }
287
288 // The token fetching flow:
289 // 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called
290 // and the subject token is received in OnRetrieveSubjectTokenInternal().
291 // 2. Exchange token - ExchangeToken() gets called with the
292 // subject token from #1. Receive the response in OnExchangeTokenInternal().
293 // 3. (Optional) Impersonate service account - ImpersenateServiceAccount() gets
294 // called with the access token of the response from #2. Get an impersonated
295 // access token in OnImpersenateServiceAccountInternal().
296 // 4. Finish token fetch - Return back the response that contains an access
297 // token in FinishTokenFetch().
298 // TODO(chuanr): Avoid starting the remaining requests if the channel gets shut
299 // down.
fetch_oauth2(grpc_credentials_metadata_request * metadata_req,grpc_polling_entity * pollent,grpc_iomgr_cb_func response_cb,Timestamp deadline)300 void ExternalAccountCredentials::fetch_oauth2(
301 grpc_credentials_metadata_request* metadata_req,
302 grpc_polling_entity* pollent, grpc_iomgr_cb_func response_cb,
303 Timestamp deadline) {
304 GPR_ASSERT(ctx_ == nullptr);
305 ctx_ = new HTTPRequestContext(pollent, deadline);
306 metadata_req_ = metadata_req;
307 response_cb_ = response_cb;
308 auto cb = [this](std::string token, grpc_error_handle error) {
309 OnRetrieveSubjectTokenInternal(token, error);
310 };
311 RetrieveSubjectToken(ctx_, options_, cb);
312 }
313
OnRetrieveSubjectTokenInternal(absl::string_view subject_token,grpc_error_handle error)314 void ExternalAccountCredentials::OnRetrieveSubjectTokenInternal(
315 absl::string_view subject_token, grpc_error_handle error) {
316 if (!error.ok()) {
317 FinishTokenFetch(error);
318 } else {
319 ExchangeToken(subject_token);
320 }
321 }
322
ExchangeToken(absl::string_view subject_token)323 void ExternalAccountCredentials::ExchangeToken(
324 absl::string_view subject_token) {
325 absl::StatusOr<URI> uri = URI::Parse(options_.token_url);
326 if (!uri.ok()) {
327 FinishTokenFetch(GRPC_ERROR_CREATE(
328 absl::StrFormat("Invalid token url: %s. Error: %s", options_.token_url,
329 uri.status().ToString())));
330 return;
331 }
332 grpc_http_request request;
333 memset(&request, 0, sizeof(grpc_http_request));
334 const bool add_authorization_header =
335 !options_.client_id.empty() && !options_.client_secret.empty();
336 request.hdr_count = add_authorization_header ? 3 : 2;
337 auto* headers = static_cast<grpc_http_header*>(
338 gpr_malloc(sizeof(grpc_http_header) * request.hdr_count));
339 headers[0].key = gpr_strdup("Content-Type");
340 headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
341 headers[1].key = gpr_strdup("x-goog-api-client");
342 headers[1].value = gpr_strdup(MetricsHeaderValue().c_str());
343 if (add_authorization_header) {
344 std::string raw_cred =
345 absl::StrFormat("%s:%s", options_.client_id, options_.client_secret);
346 std::string str = absl::StrFormat("Basic %s", absl::Base64Escape(raw_cred));
347 headers[2].key = gpr_strdup("Authorization");
348 headers[2].value = gpr_strdup(str.c_str());
349 }
350 request.hdrs = headers;
351 std::vector<std::string> body_parts;
352 body_parts.push_back(
353 absl::StrFormat("audience=%s", UrlEncode(options_.audience).c_str()));
354 body_parts.push_back(absl::StrFormat(
355 "grant_type=%s",
356 UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE).c_str()));
357 body_parts.push_back(absl::StrFormat(
358 "requested_token_type=%s",
359 UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE).c_str()));
360 body_parts.push_back(absl::StrFormat(
361 "subject_token_type=%s", UrlEncode(options_.subject_token_type).c_str()));
362 body_parts.push_back(
363 absl::StrFormat("subject_token=%s", UrlEncode(subject_token).c_str()));
364 std::string scope = GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE;
365 if (options_.service_account_impersonation_url.empty()) {
366 scope = absl::StrJoin(scopes_, " ");
367 }
368 body_parts.push_back(absl::StrFormat("scope=%s", UrlEncode(scope).c_str()));
369 Json::Object addtional_options_json_object;
370 if (options_.client_id.empty() && options_.client_secret.empty()) {
371 addtional_options_json_object["userProject"] =
372 Json::FromString(options_.workforce_pool_user_project);
373 }
374 Json addtional_options_json =
375 Json::FromObject(std::move(addtional_options_json_object));
376 body_parts.push_back(absl::StrFormat(
377 "options=%s", UrlEncode(JsonDump(addtional_options_json)).c_str()));
378 std::string body = absl::StrJoin(body_parts, "&");
379 request.body = const_cast<char*>(body.c_str());
380 request.body_length = body.size();
381 grpc_http_response_destroy(&ctx_->response);
382 ctx_->response = {};
383 GRPC_CLOSURE_INIT(&ctx_->closure, OnExchangeToken, this, nullptr);
384 GPR_ASSERT(http_request_ == nullptr);
385 RefCountedPtr<grpc_channel_credentials> http_request_creds;
386 if (uri->scheme() == "http") {
387 http_request_creds = RefCountedPtr<grpc_channel_credentials>(
388 grpc_insecure_credentials_create());
389 } else {
390 http_request_creds = CreateHttpRequestSSLCredentials();
391 }
392 http_request_ =
393 HttpRequest::Post(std::move(*uri), nullptr /* channel args */,
394 ctx_->pollent, &request, ctx_->deadline, &ctx_->closure,
395 &ctx_->response, std::move(http_request_creds));
396 http_request_->Start();
397 request.body = nullptr;
398 grpc_http_request_destroy(&request);
399 }
400
OnExchangeToken(void * arg,grpc_error_handle error)401 void ExternalAccountCredentials::OnExchangeToken(void* arg,
402 grpc_error_handle error) {
403 ExternalAccountCredentials* self =
404 static_cast<ExternalAccountCredentials*>(arg);
405 self->OnExchangeTokenInternal(error);
406 }
407
OnExchangeTokenInternal(grpc_error_handle error)408 void ExternalAccountCredentials::OnExchangeTokenInternal(
409 grpc_error_handle error) {
410 http_request_.reset();
411 if (!error.ok()) {
412 FinishTokenFetch(error);
413 } else {
414 if (options_.service_account_impersonation_url.empty()) {
415 metadata_req_->response = ctx_->response;
416 metadata_req_->response.body = gpr_strdup(
417 std::string(ctx_->response.body, ctx_->response.body_length).c_str());
418 metadata_req_->response.hdrs = static_cast<grpc_http_header*>(
419 gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count));
420 for (size_t i = 0; i < ctx_->response.hdr_count; i++) {
421 metadata_req_->response.hdrs[i].key =
422 gpr_strdup(ctx_->response.hdrs[i].key);
423 metadata_req_->response.hdrs[i].value =
424 gpr_strdup(ctx_->response.hdrs[i].value);
425 }
426 FinishTokenFetch(absl::OkStatus());
427 } else {
428 ImpersenateServiceAccount();
429 }
430 }
431 }
432
ImpersenateServiceAccount()433 void ExternalAccountCredentials::ImpersenateServiceAccount() {
434 absl::string_view response_body(ctx_->response.body,
435 ctx_->response.body_length);
436 auto json = JsonParse(response_body);
437 if (!json.ok()) {
438 FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrCat(
439 "Invalid token exchange response: ", json.status().ToString())));
440 return;
441 }
442 if (json->type() != Json::Type::kObject) {
443 FinishTokenFetch(GRPC_ERROR_CREATE(
444 "Invalid token exchange response: JSON type is not object"));
445 return;
446 }
447 auto it = json->object().find("access_token");
448 if (it == json->object().end() || it->second.type() != Json::Type::kString) {
449 FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
450 "Missing or invalid access_token in %s.", response_body)));
451 return;
452 }
453 std::string access_token = it->second.string();
454 absl::StatusOr<URI> uri =
455 URI::Parse(options_.service_account_impersonation_url);
456 if (!uri.ok()) {
457 FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
458 "Invalid service account impersonation url: %s. Error: %s",
459 options_.service_account_impersonation_url, uri.status().ToString())));
460 return;
461 }
462 grpc_http_request request;
463 memset(&request, 0, sizeof(grpc_http_request));
464 request.hdr_count = 2;
465 grpc_http_header* headers = static_cast<grpc_http_header*>(
466 gpr_malloc(sizeof(grpc_http_header) * request.hdr_count));
467 headers[0].key = gpr_strdup("Content-Type");
468 headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
469 std::string str = absl::StrFormat("Bearer %s", access_token);
470 headers[1].key = gpr_strdup("Authorization");
471 headers[1].value = gpr_strdup(str.c_str());
472 request.hdrs = headers;
473 std::vector<std::string> body_members;
474 std::string scope = absl::StrJoin(scopes_, " ");
475 body_members.push_back(absl::StrFormat("scope=%s", UrlEncode(scope).c_str()));
476 body_members.push_back(absl::StrFormat(
477 "lifetime=%ds",
478 options_.service_account_impersonation.token_lifetime_seconds));
479 std::string body = absl::StrJoin(body_members, "&");
480 request.body = const_cast<char*>(body.c_str());
481 request.body_length = body.size();
482 grpc_http_response_destroy(&ctx_->response);
483 ctx_->response = {};
484 GRPC_CLOSURE_INIT(&ctx_->closure, OnImpersenateServiceAccount, this, nullptr);
485 // TODO(ctiller): Use the callers resource quota.
486 GPR_ASSERT(http_request_ == nullptr);
487 RefCountedPtr<grpc_channel_credentials> http_request_creds;
488 if (uri->scheme() == "http") {
489 http_request_creds = RefCountedPtr<grpc_channel_credentials>(
490 grpc_insecure_credentials_create());
491 } else {
492 http_request_creds = CreateHttpRequestSSLCredentials();
493 }
494 http_request_ = HttpRequest::Post(
495 std::move(*uri), nullptr, ctx_->pollent, &request, ctx_->deadline,
496 &ctx_->closure, &ctx_->response, std::move(http_request_creds));
497 http_request_->Start();
498 request.body = nullptr;
499 grpc_http_request_destroy(&request);
500 }
501
OnImpersenateServiceAccount(void * arg,grpc_error_handle error)502 void ExternalAccountCredentials::OnImpersenateServiceAccount(
503 void* arg, grpc_error_handle error) {
504 ExternalAccountCredentials* self =
505 static_cast<ExternalAccountCredentials*>(arg);
506 self->OnImpersenateServiceAccountInternal(error);
507 }
508
OnImpersenateServiceAccountInternal(grpc_error_handle error)509 void ExternalAccountCredentials::OnImpersenateServiceAccountInternal(
510 grpc_error_handle error) {
511 http_request_.reset();
512 if (!error.ok()) {
513 FinishTokenFetch(error);
514 return;
515 }
516 absl::string_view response_body(ctx_->response.body,
517 ctx_->response.body_length);
518 auto json = JsonParse(response_body);
519 if (!json.ok()) {
520 FinishTokenFetch(GRPC_ERROR_CREATE(
521 absl::StrCat("Invalid service account impersonation response: ",
522 json.status().ToString())));
523 return;
524 }
525 if (json->type() != Json::Type::kObject) {
526 FinishTokenFetch(
527 GRPC_ERROR_CREATE("Invalid service account impersonation response: "
528 "JSON type is not object"));
529 return;
530 }
531 auto it = json->object().find("accessToken");
532 if (it == json->object().end() || it->second.type() != Json::Type::kString) {
533 FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
534 "Missing or invalid accessToken in %s.", response_body)));
535 return;
536 }
537 std::string access_token = it->second.string();
538 it = json->object().find("expireTime");
539 if (it == json->object().end() || it->second.type() != Json::Type::kString) {
540 FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
541 "Missing or invalid expireTime in %s.", response_body)));
542 return;
543 }
544 std::string expire_time = it->second.string();
545 absl::Time t;
546 if (!absl::ParseTime(absl::RFC3339_full, expire_time, &t, nullptr)) {
547 FinishTokenFetch(GRPC_ERROR_CREATE(
548 "Invalid expire time of service account impersonation response."));
549 return;
550 }
551 int64_t expire_in = (t - absl::Now()) / absl::Seconds(1);
552 std::string body = absl::StrFormat(
553 "{\"access_token\":\"%s\",\"expires_in\":%d,\"token_type\":\"Bearer\"}",
554 access_token, expire_in);
555 metadata_req_->response = ctx_->response;
556 metadata_req_->response.body = gpr_strdup(body.c_str());
557 metadata_req_->response.body_length = body.length();
558 metadata_req_->response.hdrs = static_cast<grpc_http_header*>(
559 gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count));
560 for (size_t i = 0; i < ctx_->response.hdr_count; i++) {
561 metadata_req_->response.hdrs[i].key =
562 gpr_strdup(ctx_->response.hdrs[i].key);
563 metadata_req_->response.hdrs[i].value =
564 gpr_strdup(ctx_->response.hdrs[i].value);
565 }
566 FinishTokenFetch(absl::OkStatus());
567 }
568
FinishTokenFetch(grpc_error_handle error)569 void ExternalAccountCredentials::FinishTokenFetch(grpc_error_handle error) {
570 GRPC_LOG_IF_ERROR("Fetch external account credentials access token", error);
571 // Move object state into local variables.
572 auto* cb = response_cb_;
573 response_cb_ = nullptr;
574 auto* metadata_req = metadata_req_;
575 metadata_req_ = nullptr;
576 auto* ctx = ctx_;
577 ctx_ = nullptr;
578 // Invoke the callback.
579 cb(metadata_req, error);
580 // Delete context.
581 delete ctx;
582 }
583
584 } // namespace grpc_core
585
grpc_external_account_credentials_create(const char * json_string,const char * scopes_string)586 grpc_call_credentials* grpc_external_account_credentials_create(
587 const char* json_string, const char* scopes_string) {
588 auto json = grpc_core::JsonParse(json_string);
589 if (!json.ok()) {
590 gpr_log(GPR_ERROR,
591 "External account credentials creation failed. Error: %s.",
592 json.status().ToString().c_str());
593 return nullptr;
594 }
595 std::vector<std::string> scopes = absl::StrSplit(scopes_string, ',');
596 grpc_error_handle error;
597 auto creds = grpc_core::ExternalAccountCredentials::Create(
598 *json, std::move(scopes), &error)
599 .release();
600 if (!error.ok()) {
601 gpr_log(GPR_ERROR,
602 "External account credentials creation failed. Error: %s.",
603 grpc_core::StatusToString(error).c_str());
604 return nullptr;
605 }
606 return creds;
607 }
608