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