• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2020 gRPC authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //     http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 #include <grpc/support/port_platform.h>
17 
18 #include "src/core/lib/security/credentials/external/external_account_credentials.h"
19 
20 #include "absl/strings/str_format.h"
21 #include "absl/strings/str_join.h"
22 #include "absl/strings/str_split.h"
23 #include "absl/time/clock.h"
24 #include "absl/time/time.h"
25 
26 #include "src/core/lib/http/parser.h"
27 #include "src/core/lib/security/util/json_util.h"
28 #include "src/core/lib/slice/b64.h"
29 
30 #include "src/core/lib/security/credentials/external/aws_external_account_credentials.h"
31 #include "src/core/lib/security/credentials/external/file_external_account_credentials.h"
32 #include "src/core/lib/security/credentials/external/url_external_account_credentials.h"
33 
34 #define EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE \
35   "urn:ietf:params:oauth:grant-type:token-exchange"
36 #define EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE \
37   "urn:ietf:params:oauth:token-type:access_token"
38 #define GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE \
39   "https://www.googleapis.com/auth/cloud-platform"
40 
41 namespace grpc_core {
42 
43 namespace {
44 
UrlEncode(const absl::string_view & s)45 std::string UrlEncode(const absl::string_view& s) {
46   const char* hex = "0123456789ABCDEF";
47   std::string result;
48   result.reserve(s.length());
49   for (auto c : s) {
50     if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
51         (c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '!' ||
52         c == '\'' || c == '(' || c == ')' || c == '*' || c == '~' || c == '.') {
53       result.push_back(c);
54     } else {
55       result.push_back('%');
56       result.push_back(hex[static_cast<unsigned char>(c) >> 4]);
57       result.push_back(hex[static_cast<unsigned char>(c) & 15]);
58     }
59   }
60   return result;
61 }
62 
63 }  // namespace
64 
Create(const Json & json,std::vector<std::string> scopes,grpc_error ** error)65 RefCountedPtr<ExternalAccountCredentials> ExternalAccountCredentials::Create(
66     const Json& json, std::vector<std::string> scopes, grpc_error** error) {
67   GPR_ASSERT(*error == GRPC_ERROR_NONE);
68   Options options;
69   options.type = GRPC_AUTH_JSON_TYPE_INVALID;
70   if (json.type() != Json::Type::OBJECT) {
71     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
72         "Invalid json to construct credentials options.");
73     return nullptr;
74   }
75   auto it = json.object_value().find("type");
76   if (it == json.object_value().end()) {
77     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING("type field not present.");
78     return nullptr;
79   }
80   if (it->second.type() != Json::Type::STRING) {
81     *error =
82         GRPC_ERROR_CREATE_FROM_STATIC_STRING("type field must be a string.");
83     return nullptr;
84   }
85   if (it->second.string_value() != GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT) {
86     *error =
87         GRPC_ERROR_CREATE_FROM_STATIC_STRING("Invalid credentials json type.");
88     return nullptr;
89   }
90   options.type = GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT;
91   it = json.object_value().find("audience");
92   if (it == json.object_value().end()) {
93     *error =
94         GRPC_ERROR_CREATE_FROM_STATIC_STRING("audience field not present.");
95     return nullptr;
96   }
97   if (it->second.type() != Json::Type::STRING) {
98     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
99         "audience field must be a string.");
100     return nullptr;
101   }
102   options.audience = it->second.string_value();
103   it = json.object_value().find("subject_token_type");
104   if (it == json.object_value().end()) {
105     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
106         "subject_token_type field not present.");
107     return nullptr;
108   }
109   if (it->second.type() != Json::Type::STRING) {
110     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
111         "subject_token_type field must be a string.");
112     return nullptr;
113   }
114   options.subject_token_type = it->second.string_value();
115   it = json.object_value().find("service_account_impersonation_url");
116   if (it != json.object_value().end()) {
117     options.service_account_impersonation_url = it->second.string_value();
118   }
119   it = json.object_value().find("token_url");
120   if (it == json.object_value().end()) {
121     *error =
122         GRPC_ERROR_CREATE_FROM_STATIC_STRING("token_url field not present.");
123     return nullptr;
124   }
125   if (it->second.type() != Json::Type::STRING) {
126     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
127         "token_url field must be a string.");
128     return nullptr;
129   }
130   options.token_url = it->second.string_value();
131   it = json.object_value().find("token_info_url");
132   if (it != json.object_value().end()) {
133     options.token_info_url = it->second.string_value();
134   }
135   it = json.object_value().find("credential_source");
136   if (it == json.object_value().end()) {
137     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
138         "credential_source field not present.");
139     return nullptr;
140   }
141   options.credential_source = it->second;
142   it = json.object_value().find("quota_project_id");
143   if (it != json.object_value().end()) {
144     options.quota_project_id = it->second.string_value();
145   }
146   it = json.object_value().find("client_id");
147   if (it != json.object_value().end()) {
148     options.client_id = it->second.string_value();
149   }
150   it = json.object_value().find("client_secret");
151   if (it != json.object_value().end()) {
152     options.client_secret = it->second.string_value();
153   }
154   RefCountedPtr<ExternalAccountCredentials> creds;
155   if (options.credential_source.object_value().find("environment_id") !=
156       options.credential_source.object_value().end()) {
157     creds = MakeRefCounted<AwsExternalAccountCredentials>(
158         std::move(options), std::move(scopes), error);
159   } else if (options.credential_source.object_value().find("file") !=
160              options.credential_source.object_value().end()) {
161     creds = MakeRefCounted<FileExternalAccountCredentials>(
162         std::move(options), std::move(scopes), error);
163   } else if (options.credential_source.object_value().find("url") !=
164              options.credential_source.object_value().end()) {
165     creds = MakeRefCounted<UrlExternalAccountCredentials>(
166         std::move(options), std::move(scopes), error);
167   } else {
168     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
169         "Invalid options credential source to create "
170         "ExternalAccountCredentials.");
171   }
172   if (*error == GRPC_ERROR_NONE) {
173     return creds;
174   } else {
175     return nullptr;
176   }
177 }
178 
ExternalAccountCredentials(Options options,std::vector<std::string> scopes)179 ExternalAccountCredentials::ExternalAccountCredentials(
180     Options options, std::vector<std::string> scopes)
181     : options_(std::move(options)) {
182   if (scopes.empty()) {
183     scopes.push_back(GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE);
184   }
185   scopes_ = std::move(scopes);
186 }
187 
~ExternalAccountCredentials()188 ExternalAccountCredentials::~ExternalAccountCredentials() {}
189 
debug_string()190 std::string ExternalAccountCredentials::debug_string() {
191   return absl::StrFormat("ExternalAccountCredentials{Audience:%s,%s}",
192                          options_.audience,
193                          grpc_oauth2_token_fetcher_credentials::debug_string());
194 }
195 
196 // The token fetching flow:
197 // 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called
198 // and the subject token is received in OnRetrieveSubjectTokenInternal().
199 // 2. Exchange token - ExchangeToken() gets called with the
200 // subject token from #1. Receive the response in OnExchangeTokenInternal().
201 // 3. (Optional) Impersonate service account - ImpersenateServiceAccount() gets
202 // called with the access token of the response from #2. Get an impersonated
203 // access token in OnImpersenateServiceAccountInternal().
204 // 4. Finish token fetch - Return back the response that contains an access
205 // token in FinishTokenFetch().
206 // TODO(chuanr): Avoid starting the remaining requests if the channel gets shut
207 // down.
fetch_oauth2(grpc_credentials_metadata_request * metadata_req,grpc_httpcli_context * httpcli_context,grpc_polling_entity * pollent,grpc_iomgr_cb_func response_cb,grpc_millis deadline)208 void ExternalAccountCredentials::fetch_oauth2(
209     grpc_credentials_metadata_request* metadata_req,
210     grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent,
211     grpc_iomgr_cb_func response_cb, grpc_millis deadline) {
212   GPR_ASSERT(ctx_ == nullptr);
213   ctx_ = new HTTPRequestContext(httpcli_context, pollent, deadline);
214   metadata_req_ = metadata_req;
215   response_cb_ = response_cb;
216   auto cb = [this](std::string token, grpc_error* error) {
217     OnRetrieveSubjectTokenInternal(token, error);
218   };
219   RetrieveSubjectToken(ctx_, options_, cb);
220 }
221 
OnRetrieveSubjectTokenInternal(absl::string_view subject_token,grpc_error * error)222 void ExternalAccountCredentials::OnRetrieveSubjectTokenInternal(
223     absl::string_view subject_token, grpc_error* error) {
224   if (error != GRPC_ERROR_NONE) {
225     FinishTokenFetch(error);
226   } else {
227     ExchangeToken(subject_token);
228   }
229 }
230 
ExchangeToken(absl::string_view subject_token)231 void ExternalAccountCredentials::ExchangeToken(
232     absl::string_view subject_token) {
233   absl::StatusOr<URI> uri = URI::Parse(options_.token_url);
234   if (!uri.ok()) {
235     FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
236         absl::StrFormat("Invalid token url: %s. Error: %s", options_.token_url,
237                         uri.status().ToString())
238             .c_str()));
239     return;
240   }
241   grpc_httpcli_request request;
242   memset(&request, 0, sizeof(grpc_httpcli_request));
243   request.host = const_cast<char*>(uri->authority().c_str());
244   request.http.path = gpr_strdup(uri->path().c_str());
245   grpc_http_header* headers = nullptr;
246   if (!options_.client_id.empty() && !options_.client_secret.empty()) {
247     request.http.hdr_count = 2;
248     headers = static_cast<grpc_http_header*>(
249         gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count));
250     headers[0].key = gpr_strdup("Content-Type");
251     headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
252     std::string raw_cred =
253         absl::StrFormat("%s:%s", options_.client_id, options_.client_secret);
254     char* encoded_cred =
255         grpc_base64_encode(raw_cred.c_str(), raw_cred.length(), 0, 0);
256     std::string str = absl::StrFormat("Basic %s", std::string(encoded_cred));
257     headers[1].key = gpr_strdup("Authorization");
258     headers[1].value = gpr_strdup(str.c_str());
259     gpr_free(encoded_cred);
260   } else {
261     request.http.hdr_count = 1;
262     headers = static_cast<grpc_http_header*>(
263         gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count));
264     headers[0].key = gpr_strdup("Content-Type");
265     headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
266   }
267   request.http.hdrs = headers;
268   request.handshaker =
269       uri->scheme() == "https" ? &grpc_httpcli_ssl : &grpc_httpcli_plaintext;
270   std::vector<std::string> body_parts;
271   body_parts.push_back(absl::StrFormat("%s=%s", "audience",
272                                        UrlEncode(options_.audience).c_str()));
273   body_parts.push_back(absl::StrFormat(
274       "%s=%s", "grant_type",
275       UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE).c_str()));
276   body_parts.push_back(absl::StrFormat(
277       "%s=%s", "requested_token_type",
278       UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE).c_str()));
279   body_parts.push_back(
280       absl::StrFormat("%s=%s", "subject_token_type",
281                       UrlEncode(options_.subject_token_type).c_str()));
282   body_parts.push_back(absl::StrFormat("%s=%s", "subject_token",
283                                        UrlEncode(subject_token).c_str()));
284   std::string scope = GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE;
285   if (options_.service_account_impersonation_url.empty()) {
286     scope = absl::StrJoin(scopes_, " ");
287   }
288   body_parts.push_back(
289       absl::StrFormat("%s=%s", "scope", UrlEncode(scope).c_str()));
290   std::string body = absl::StrJoin(body_parts, "&");
291   grpc_resource_quota* resource_quota =
292       grpc_resource_quota_create("external_account_credentials");
293   grpc_http_response_destroy(&ctx_->response);
294   ctx_->response = {};
295   GRPC_CLOSURE_INIT(&ctx_->closure, OnExchangeToken, this, nullptr);
296   grpc_httpcli_post(ctx_->httpcli_context, ctx_->pollent, resource_quota,
297                     &request, body.c_str(), body.size(), ctx_->deadline,
298                     &ctx_->closure, &ctx_->response);
299   grpc_resource_quota_unref_internal(resource_quota);
300   grpc_http_request_destroy(&request.http);
301 }
302 
OnExchangeToken(void * arg,grpc_error * error)303 void ExternalAccountCredentials::OnExchangeToken(void* arg, grpc_error* error) {
304   ExternalAccountCredentials* self =
305       static_cast<ExternalAccountCredentials*>(arg);
306   self->OnExchangeTokenInternal(GRPC_ERROR_REF(error));
307 }
308 
OnExchangeTokenInternal(grpc_error * error)309 void ExternalAccountCredentials::OnExchangeTokenInternal(grpc_error* error) {
310   if (error != GRPC_ERROR_NONE) {
311     FinishTokenFetch(error);
312   } else {
313     if (options_.service_account_impersonation_url.empty()) {
314       metadata_req_->response = ctx_->response;
315       metadata_req_->response.body = gpr_strdup(
316           std::string(ctx_->response.body, ctx_->response.body_length).c_str());
317       metadata_req_->response.hdrs = static_cast<grpc_http_header*>(
318           gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count));
319       for (int i = 0; i < ctx_->response.hdr_count; i++) {
320         metadata_req_->response.hdrs[i].key =
321             gpr_strdup(ctx_->response.hdrs[i].key);
322         metadata_req_->response.hdrs[i].value =
323             gpr_strdup(ctx_->response.hdrs[i].value);
324       }
325       FinishTokenFetch(GRPC_ERROR_NONE);
326     } else {
327       ImpersenateServiceAccount();
328     }
329   }
330 }
331 
ImpersenateServiceAccount()332 void ExternalAccountCredentials::ImpersenateServiceAccount() {
333   grpc_error* error = GRPC_ERROR_NONE;
334   absl::string_view response_body(ctx_->response.body,
335                                   ctx_->response.body_length);
336   Json json = Json::Parse(response_body, &error);
337   if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) {
338     FinishTokenFetch(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
339         "Invalid token exchange response.", &error, 1));
340     GRPC_ERROR_UNREF(error);
341     return;
342   }
343   auto it = json.object_value().find("access_token");
344   if (it == json.object_value().end() ||
345       it->second.type() != Json::Type::STRING) {
346     FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
347         absl::StrFormat("Missing or invalid access_token in %s.", response_body)
348             .c_str()));
349     return;
350   }
351   std::string access_token = it->second.string_value();
352   absl::StatusOr<URI> uri =
353       URI::Parse(options_.service_account_impersonation_url);
354   if (!uri.ok()) {
355     FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
356         absl::StrFormat(
357             "Invalid service account impersonation url: %s. Error: %s",
358             options_.service_account_impersonation_url, uri.status().ToString())
359             .c_str()));
360     return;
361   }
362   grpc_httpcli_request request;
363   memset(&request, 0, sizeof(grpc_httpcli_request));
364   request.host = const_cast<char*>(uri->authority().c_str());
365   request.http.path = gpr_strdup(uri->path().c_str());
366   request.http.hdr_count = 2;
367   grpc_http_header* headers = static_cast<grpc_http_header*>(
368       gpr_malloc(sizeof(grpc_http_header) * request.http.hdr_count));
369   headers[0].key = gpr_strdup("Content-Type");
370   headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
371   std::string str = absl::StrFormat("Bearer %s", access_token);
372   headers[1].key = gpr_strdup("Authorization");
373   headers[1].value = gpr_strdup(str.c_str());
374   request.http.hdrs = headers;
375   request.handshaker =
376       uri->scheme() == "https" ? &grpc_httpcli_ssl : &grpc_httpcli_plaintext;
377   std::string scope = absl::StrJoin(scopes_, " ");
378   std::string body = absl::StrFormat("%s=%s", "scope", scope);
379   grpc_resource_quota* resource_quota =
380       grpc_resource_quota_create("external_account_credentials");
381   grpc_http_response_destroy(&ctx_->response);
382   ctx_->response = {};
383   GRPC_CLOSURE_INIT(&ctx_->closure, OnImpersenateServiceAccount, this, nullptr);
384   grpc_httpcli_post(ctx_->httpcli_context, ctx_->pollent, resource_quota,
385                     &request, body.c_str(), body.size(), ctx_->deadline,
386                     &ctx_->closure, &ctx_->response);
387   grpc_resource_quota_unref_internal(resource_quota);
388   grpc_http_request_destroy(&request.http);
389 }
390 
OnImpersenateServiceAccount(void * arg,grpc_error * error)391 void ExternalAccountCredentials::OnImpersenateServiceAccount(
392     void* arg, grpc_error* error) {
393   ExternalAccountCredentials* self =
394       static_cast<ExternalAccountCredentials*>(arg);
395   self->OnImpersenateServiceAccountInternal(GRPC_ERROR_REF(error));
396 }
397 
OnImpersenateServiceAccountInternal(grpc_error * error)398 void ExternalAccountCredentials::OnImpersenateServiceAccountInternal(
399     grpc_error* error) {
400   if (error != GRPC_ERROR_NONE) {
401     FinishTokenFetch(error);
402     return;
403   }
404   absl::string_view response_body(ctx_->response.body,
405                                   ctx_->response.body_length);
406   Json json = Json::Parse(response_body, &error);
407   if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) {
408     FinishTokenFetch(GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
409         "Invalid service account impersonation response.", &error, 1));
410     GRPC_ERROR_UNREF(error);
411     return;
412   }
413   auto it = json.object_value().find("accessToken");
414   if (it == json.object_value().end() ||
415       it->second.type() != Json::Type::STRING) {
416     FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
417         absl::StrFormat("Missing or invalid accessToken in %s.", response_body)
418             .c_str()));
419     return;
420   }
421   std::string access_token = it->second.string_value();
422   it = json.object_value().find("expireTime");
423   if (it == json.object_value().end() ||
424       it->second.type() != Json::Type::STRING) {
425     FinishTokenFetch(GRPC_ERROR_CREATE_FROM_COPIED_STRING(
426         absl::StrFormat("Missing or invalid expireTime in %s.", response_body)
427             .c_str()));
428     return;
429   }
430   std::string expire_time = it->second.string_value();
431   absl::Time t;
432   if (!absl::ParseTime(absl::RFC3339_full, expire_time, &t, nullptr)) {
433     FinishTokenFetch(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
434         "Invalid expire time of service account impersonation response."));
435     return;
436   }
437   int expire_in = (t - absl::Now()) / absl::Seconds(1);
438   std::string body = absl::StrFormat(
439       "{\"access_token\":\"%s\",\"expires_in\":%d,\"token_type\":\"Bearer\"}",
440       access_token, expire_in);
441   metadata_req_->response = ctx_->response;
442   metadata_req_->response.body = gpr_strdup(body.c_str());
443   metadata_req_->response.body_length = body.length();
444   metadata_req_->response.hdrs = static_cast<grpc_http_header*>(
445       gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count));
446   for (int i = 0; i < ctx_->response.hdr_count; i++) {
447     metadata_req_->response.hdrs[i].key =
448         gpr_strdup(ctx_->response.hdrs[i].key);
449     metadata_req_->response.hdrs[i].value =
450         gpr_strdup(ctx_->response.hdrs[i].value);
451   }
452   FinishTokenFetch(GRPC_ERROR_NONE);
453 }
454 
FinishTokenFetch(grpc_error * error)455 void ExternalAccountCredentials::FinishTokenFetch(grpc_error* error) {
456   GRPC_LOG_IF_ERROR("Fetch external account credentials access token",
457                     GRPC_ERROR_REF(error));
458   // Move object state into local variables.
459   auto* cb = response_cb_;
460   response_cb_ = nullptr;
461   auto* metadata_req = metadata_req_;
462   metadata_req_ = nullptr;
463   auto* ctx = ctx_;
464   ctx_ = nullptr;
465   // Invoke the callback.
466   cb(metadata_req, error);
467   // Delete context.
468   delete ctx;
469   GRPC_ERROR_UNREF(error);
470 }
471 
472 }  // namespace grpc_core
473 
grpc_external_account_credentials_create(const char * json_string,const char * scopes_string)474 grpc_call_credentials* grpc_external_account_credentials_create(
475     const char* json_string, const char* scopes_string) {
476   grpc_error* error = GRPC_ERROR_NONE;
477   grpc_core::Json json = grpc_core::Json::Parse(json_string, &error);
478   if (error != GRPC_ERROR_NONE) {
479     gpr_log(GPR_ERROR,
480             "External account credentials creation failed. Error: %s.",
481             grpc_error_string(error));
482     GRPC_ERROR_UNREF(error);
483     return nullptr;
484   }
485   std::vector<std::string> scopes = absl::StrSplit(scopes_string, ',');
486   auto creds = grpc_core::ExternalAccountCredentials::Create(
487                    json, std::move(scopes), &error)
488                    .release();
489   if (error != GRPC_ERROR_NONE) {
490     gpr_log(GPR_ERROR,
491             "External account credentials creation failed. Error: %s.",
492             grpc_error_string(error));
493     GRPC_ERROR_UNREF(error);
494     return nullptr;
495   }
496   return creds;
497 }
498