• 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/aws_external_account_credentials.h"
19 
20 #include "absl/strings/str_format.h"
21 #include "absl/strings/str_join.h"
22 #include "absl/strings/str_replace.h"
23 
24 #include "src/core/lib/gpr/env.h"
25 
26 namespace grpc_core {
27 
28 namespace {
29 
30 const char* kExpectedEnvironmentId = "aws1";
31 
32 const char* kRegionEnvVar = "AWS_REGION";
33 const char* kAccessKeyIdEnvVar = "AWS_ACCESS_KEY_ID";
34 const char* kSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY";
35 const char* kSessionTokenEnvVar = "AWS_SESSION_TOKEN";
36 
UrlEncode(const absl::string_view & s)37 std::string UrlEncode(const absl::string_view& s) {
38   const char* hex = "0123456789ABCDEF";
39   std::string result;
40   result.reserve(s.length());
41   for (auto c : s) {
42     if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
43         (c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '!' ||
44         c == '\'' || c == '(' || c == ')' || c == '*' || c == '~' || c == '.') {
45       result.push_back(c);
46     } else {
47       result.push_back('%');
48       result.push_back(hex[static_cast<unsigned char>(c) >> 4]);
49       result.push_back(hex[static_cast<unsigned char>(c) & 15]);
50     }
51   }
52   return result;
53 }
54 
55 }  // namespace
56 
57 RefCountedPtr<AwsExternalAccountCredentials>
Create(Options options,std::vector<std::string> scopes,grpc_error ** error)58 AwsExternalAccountCredentials::Create(Options options,
59                                       std::vector<std::string> scopes,
60                                       grpc_error** error) {
61   auto creds = MakeRefCounted<AwsExternalAccountCredentials>(
62       std::move(options), std::move(scopes), error);
63   if (*error == GRPC_ERROR_NONE) {
64     return creds;
65   } else {
66     return nullptr;
67   }
68 }
69 
AwsExternalAccountCredentials(Options options,std::vector<std::string> scopes,grpc_error ** error)70 AwsExternalAccountCredentials::AwsExternalAccountCredentials(
71     Options options, std::vector<std::string> scopes, grpc_error** error)
72     : ExternalAccountCredentials(options, std::move(scopes)) {
73   audience_ = options.audience;
74   auto it = options.credential_source.object_value().find("environment_id");
75   if (it == options.credential_source.object_value().end()) {
76     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
77         "environment_id field not present.");
78     return;
79   }
80   if (it->second.type() != Json::Type::STRING) {
81     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
82         "environment_id field must be a string.");
83     return;
84   }
85   if (it->second.string_value() != kExpectedEnvironmentId) {
86     *error =
87         GRPC_ERROR_CREATE_FROM_STATIC_STRING("environment_id does not match.");
88     return;
89   }
90   it = options.credential_source.object_value().find("region_url");
91   if (it == options.credential_source.object_value().end()) {
92     *error =
93         GRPC_ERROR_CREATE_FROM_STATIC_STRING("region_url field not present.");
94     return;
95   }
96   if (it->second.type() != Json::Type::STRING) {
97     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
98         "region_url field must be a string.");
99     return;
100   }
101   region_url_ = it->second.string_value();
102   it = options.credential_source.object_value().find("url");
103   if (it != options.credential_source.object_value().end() &&
104       it->second.type() == Json::Type::STRING) {
105     url_ = it->second.string_value();
106   }
107   it = options.credential_source.object_value().find(
108       "regional_cred_verification_url");
109   if (it == options.credential_source.object_value().end()) {
110     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
111         "regional_cred_verification_url field not present.");
112     return;
113   }
114   if (it->second.type() != Json::Type::STRING) {
115     *error = GRPC_ERROR_CREATE_FROM_STATIC_STRING(
116         "regional_cred_verification_url field must be a string.");
117     return;
118   }
119   regional_cred_verification_url_ = it->second.string_value();
120 }
121 
RetrieveSubjectToken(HTTPRequestContext * ctx,const Options & options,std::function<void (std::string,grpc_error *)> cb)122 void AwsExternalAccountCredentials::RetrieveSubjectToken(
123     HTTPRequestContext* ctx, const Options& options,
124     std::function<void(std::string, grpc_error*)> cb) {
125   if (ctx == nullptr) {
126     FinishRetrieveSubjectToken(
127         "",
128         GRPC_ERROR_CREATE_FROM_STATIC_STRING(
129             "Missing HTTPRequestContext to start subject token retrieval."));
130     return;
131   }
132   ctx_ = ctx;
133   cb_ = cb;
134   if (signer_ != nullptr) {
135     BuildSubjectToken();
136   } else {
137     RetrieveRegion();
138   }
139 }
140 
RetrieveRegion()141 void AwsExternalAccountCredentials::RetrieveRegion() {
142   UniquePtr<char> region_from_env(gpr_getenv(kRegionEnvVar));
143   if (region_from_env != nullptr) {
144     region_ = std::string(region_from_env.get());
145     if (url_.empty()) {
146       RetrieveSigningKeys();
147     } else {
148       RetrieveRoleName();
149     }
150     return;
151   }
152   absl::StatusOr<URI> uri = URI::Parse(region_url_);
153   if (!uri.ok()) {
154     FinishRetrieveSubjectToken("", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
155                                        absl::StrFormat("Invalid region url. %s",
156                                                        uri.status().ToString())
157                                            .c_str()));
158     return;
159   }
160   grpc_httpcli_request request;
161   memset(&request, 0, sizeof(grpc_httpcli_request));
162   request.host = const_cast<char*>(uri->authority().c_str());
163   request.http.path = gpr_strdup(uri->path().c_str());
164   request.handshaker =
165       uri->scheme() == "https" ? &grpc_httpcli_ssl : &grpc_httpcli_plaintext;
166   grpc_resource_quota* resource_quota =
167       grpc_resource_quota_create("external_account_credentials");
168   grpc_http_response_destroy(&ctx_->response);
169   ctx_->response = {};
170   GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveRegion, this, nullptr);
171   grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
172                    &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
173   grpc_resource_quota_unref_internal(resource_quota);
174   grpc_http_request_destroy(&request.http);
175 }
176 
OnRetrieveRegion(void * arg,grpc_error * error)177 void AwsExternalAccountCredentials::OnRetrieveRegion(void* arg,
178                                                      grpc_error* error) {
179   AwsExternalAccountCredentials* self =
180       static_cast<AwsExternalAccountCredentials*>(arg);
181   self->OnRetrieveRegionInternal(GRPC_ERROR_REF(error));
182 }
183 
OnRetrieveRegionInternal(grpc_error * error)184 void AwsExternalAccountCredentials::OnRetrieveRegionInternal(
185     grpc_error* error) {
186   if (error != GRPC_ERROR_NONE) {
187     FinishRetrieveSubjectToken("", error);
188     return;
189   }
190   // Remove the last letter of availability zone to get pure region
191   absl::string_view response_body(ctx_->response.body,
192                                   ctx_->response.body_length);
193   region_ = std::string(response_body.substr(0, response_body.size() - 1));
194   if (url_.empty()) {
195     RetrieveSigningKeys();
196   } else {
197     RetrieveRoleName();
198   }
199 }
200 
RetrieveRoleName()201 void AwsExternalAccountCredentials::RetrieveRoleName() {
202   absl::StatusOr<URI> uri = URI::Parse(url_);
203   if (!uri.ok()) {
204     FinishRetrieveSubjectToken(
205         "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
206                 absl::StrFormat("Invalid url: %s.", uri.status().ToString())
207                     .c_str()));
208     return;
209   }
210   grpc_httpcli_request request;
211   memset(&request, 0, sizeof(grpc_httpcli_request));
212   request.host = const_cast<char*>(uri->authority().c_str());
213   request.http.path = gpr_strdup(uri->path().c_str());
214   request.handshaker =
215       uri->scheme() == "https" ? &grpc_httpcli_ssl : &grpc_httpcli_plaintext;
216   grpc_resource_quota* resource_quota =
217       grpc_resource_quota_create("external_account_credentials");
218   grpc_http_response_destroy(&ctx_->response);
219   ctx_->response = {};
220   GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveRoleName, this, nullptr);
221   grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
222                    &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
223   grpc_resource_quota_unref_internal(resource_quota);
224   grpc_http_request_destroy(&request.http);
225 }
226 
OnRetrieveRoleName(void * arg,grpc_error * error)227 void AwsExternalAccountCredentials::OnRetrieveRoleName(void* arg,
228                                                        grpc_error* error) {
229   AwsExternalAccountCredentials* self =
230       static_cast<AwsExternalAccountCredentials*>(arg);
231   self->OnRetrieveRoleNameInternal(GRPC_ERROR_REF(error));
232 }
233 
OnRetrieveRoleNameInternal(grpc_error * error)234 void AwsExternalAccountCredentials::OnRetrieveRoleNameInternal(
235     grpc_error* error) {
236   if (error != GRPC_ERROR_NONE) {
237     FinishRetrieveSubjectToken("", error);
238     return;
239   }
240   role_name_ = std::string(ctx_->response.body, ctx_->response.body_length);
241   RetrieveSigningKeys();
242 }
243 
RetrieveSigningKeys()244 void AwsExternalAccountCredentials::RetrieveSigningKeys() {
245   UniquePtr<char> access_key_id_from_env(gpr_getenv(kAccessKeyIdEnvVar));
246   UniquePtr<char> secret_access_key_from_env(
247       gpr_getenv(kSecretAccessKeyEnvVar));
248   UniquePtr<char> token_from_env(gpr_getenv(kSessionTokenEnvVar));
249   if (access_key_id_from_env != nullptr &&
250       secret_access_key_from_env != nullptr && token_from_env != nullptr) {
251     access_key_id_ = std::string(access_key_id_from_env.get());
252     secret_access_key_ = std::string(secret_access_key_from_env.get());
253     token_ = std::string(token_from_env.get());
254     BuildSubjectToken();
255     return;
256   }
257   if (role_name_.empty()) {
258     FinishRetrieveSubjectToken(
259         "", GRPC_ERROR_CREATE_FROM_STATIC_STRING(
260                 "Missing role name when retrieving signing keys."));
261     return;
262   }
263   std::string url_with_role_name = absl::StrCat(url_, "/", role_name_);
264   absl::StatusOr<URI> uri = URI::Parse(url_with_role_name);
265   if (!uri.ok()) {
266     FinishRetrieveSubjectToken(
267         "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
268                 absl::StrFormat("Invalid url with role name: %s.",
269                                 uri.status().ToString())
270                     .c_str()));
271     return;
272   }
273   grpc_httpcli_request request;
274   memset(&request, 0, sizeof(grpc_httpcli_request));
275   request.host = const_cast<char*>(uri->authority().c_str());
276   request.http.path = gpr_strdup(uri->path().c_str());
277   request.handshaker =
278       uri->scheme() == "https" ? &grpc_httpcli_ssl : &grpc_httpcli_plaintext;
279   grpc_resource_quota* resource_quota =
280       grpc_resource_quota_create("external_account_credentials");
281   grpc_http_response_destroy(&ctx_->response);
282   ctx_->response = {};
283   GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSigningKeys, this, nullptr);
284   grpc_httpcli_get(ctx_->httpcli_context, ctx_->pollent, resource_quota,
285                    &request, ctx_->deadline, &ctx_->closure, &ctx_->response);
286   grpc_resource_quota_unref_internal(resource_quota);
287   grpc_http_request_destroy(&request.http);
288 }
289 
OnRetrieveSigningKeys(void * arg,grpc_error * error)290 void AwsExternalAccountCredentials::OnRetrieveSigningKeys(void* arg,
291                                                           grpc_error* error) {
292   AwsExternalAccountCredentials* self =
293       static_cast<AwsExternalAccountCredentials*>(arg);
294   self->OnRetrieveSigningKeysInternal(GRPC_ERROR_REF(error));
295 }
296 
OnRetrieveSigningKeysInternal(grpc_error * error)297 void AwsExternalAccountCredentials::OnRetrieveSigningKeysInternal(
298     grpc_error* error) {
299   if (error != GRPC_ERROR_NONE) {
300     FinishRetrieveSubjectToken("", error);
301     return;
302   }
303   absl::string_view response_body(ctx_->response.body,
304                                   ctx_->response.body_length);
305   Json json = Json::Parse(response_body, &error);
306   if (error != GRPC_ERROR_NONE || json.type() != Json::Type::OBJECT) {
307     FinishRetrieveSubjectToken(
308         "", GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
309                 "Invalid retrieve signing keys response.", &error, 1));
310     GRPC_ERROR_UNREF(error);
311     return;
312   }
313   auto it = json.object_value().find("AccessKeyId");
314   if (it != json.object_value().end() &&
315       it->second.type() == Json::Type::STRING) {
316     access_key_id_ = it->second.string_value();
317   } else {
318     FinishRetrieveSubjectToken(
319         "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
320                 absl::StrFormat("Missing or invalid AccessKeyId in %s.",
321                                 response_body)
322                     .c_str()));
323     return;
324   }
325   it = json.object_value().find("SecretAccessKey");
326   if (it != json.object_value().end() &&
327       it->second.type() == Json::Type::STRING) {
328     secret_access_key_ = it->second.string_value();
329   } else {
330     FinishRetrieveSubjectToken(
331         "", GRPC_ERROR_CREATE_FROM_COPIED_STRING(
332                 absl::StrFormat("Missing or invalid SecretAccessKey in %s.",
333                                 response_body)
334                     .c_str()));
335     return;
336   }
337   it = json.object_value().find("Token");
338   if (it != json.object_value().end() &&
339       it->second.type() == Json::Type::STRING) {
340     token_ = it->second.string_value();
341   } else {
342     FinishRetrieveSubjectToken(
343         "",
344         GRPC_ERROR_CREATE_FROM_COPIED_STRING(
345             absl::StrFormat("Missing or invalid Token in %s.", response_body)
346                 .c_str()));
347     return;
348   }
349   BuildSubjectToken();
350 }
351 
BuildSubjectToken()352 void AwsExternalAccountCredentials::BuildSubjectToken() {
353   grpc_error* error = GRPC_ERROR_NONE;
354   if (signer_ == nullptr) {
355     cred_verification_url_ = absl::StrReplaceAll(
356         regional_cred_verification_url_, {{"{region}", region_}});
357     signer_ = absl::make_unique<AwsRequestSigner>(
358         access_key_id_, secret_access_key_, token_, "POST",
359         cred_verification_url_, region_, "",
360         std::map<std::string, std::string>(), &error);
361     if (error != GRPC_ERROR_NONE) {
362       FinishRetrieveSubjectToken(
363           "", GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
364                   "Creating aws request signer failed.", &error, 1));
365       GRPC_ERROR_UNREF(error);
366       return;
367     }
368   }
369   auto signed_headers = signer_->GetSignedRequestHeaders();
370   if (error != GRPC_ERROR_NONE) {
371     FinishRetrieveSubjectToken("",
372                                GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
373                                    "Invalid getting signed request"
374                                    "headers.",
375                                    &error, 1));
376     GRPC_ERROR_UNREF(error);
377     return;
378   }
379   // Construct subject token
380   Json::Array headers;
381   headers.push_back(Json(
382       {{"key", "Authorization"}, {"value", signed_headers["Authorization"]}}));
383   headers.push_back(Json({{"key", "host"}, {"value", signed_headers["host"]}}));
384   headers.push_back(
385       Json({{"key", "x-amz-date"}, {"value", signed_headers["x-amz-date"]}}));
386   headers.push_back(Json({{"key", "x-amz-security-token"},
387                           {"value", signed_headers["x-amz-security-token"]}}));
388   headers.push_back(
389       Json({{"key", "x-goog-cloud-target-resource"}, {"value", audience_}}));
390   Json::Object object{{"url", Json(cred_verification_url_)},
391                       {"method", Json("POST")},
392                       {"headers", Json(headers)}};
393   Json subject_token_json(object);
394   std::string subject_token = UrlEncode(subject_token_json.Dump());
395   FinishRetrieveSubjectToken(subject_token, GRPC_ERROR_NONE);
396 }
397 
FinishRetrieveSubjectToken(std::string subject_token,grpc_error * error)398 void AwsExternalAccountCredentials::FinishRetrieveSubjectToken(
399     std::string subject_token, grpc_error* error) {
400   // Reset context
401   ctx_ = nullptr;
402   // Move object state into local variables.
403   auto cb = cb_;
404   cb_ = nullptr;
405   // Invoke the callback.
406   if (error != GRPC_ERROR_NONE) {
407     cb("", error);
408   } else {
409     cb(subject_token, GRPC_ERROR_NONE);
410   }
411 }
412 
413 }  // namespace grpc_core
414