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