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/url_external_account_credentials.h"
19 
20 #include <string.h>
21 
22 #include <initializer_list>
23 #include <memory>
24 #include <utility>
25 
26 #include "absl/status/status.h"
27 #include "absl/status/statusor.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_format.h"
30 #include "absl/strings/str_split.h"
31 #include "absl/strings/string_view.h"
32 
33 #include <grpc/grpc.h>
34 #include <grpc/grpc_security.h>
35 #include <grpc/support/alloc.h>
36 #include <grpc/support/json.h>
37 #include <grpc/support/log.h>
38 #include <grpc/support/string_util.h>
39 
40 #include "src/core/lib/http/httpcli_ssl_credentials.h"
41 #include "src/core/lib/http/parser.h"
42 #include "src/core/lib/iomgr/closure.h"
43 #include "src/core/lib/json/json.h"
44 #include "src/core/lib/json/json_reader.h"
45 #include "src/core/lib/security/credentials/credentials.h"
46 #include "src/core/lib/transport/error_utils.h"
47 
48 namespace grpc_core {
49 
50 RefCountedPtr<UrlExternalAccountCredentials>
Create(Options options,std::vector<std::string> scopes,grpc_error_handle * error)51 UrlExternalAccountCredentials::Create(Options options,
52                                       std::vector<std::string> scopes,
53                                       grpc_error_handle* error) {
54   auto creds = MakeRefCounted<UrlExternalAccountCredentials>(
55       std::move(options), std::move(scopes), error);
56   if (error->ok()) {
57     return creds;
58   } else {
59     return nullptr;
60   }
61 }
62 
UrlExternalAccountCredentials(Options options,std::vector<std::string> scopes,grpc_error_handle * error)63 UrlExternalAccountCredentials::UrlExternalAccountCredentials(
64     Options options, std::vector<std::string> scopes, grpc_error_handle* error)
65     : ExternalAccountCredentials(options, std::move(scopes)) {
66   auto it = options.credential_source.object().find("url");
67   if (it == options.credential_source.object().end()) {
68     *error = GRPC_ERROR_CREATE("url field not present.");
69     return;
70   }
71   if (it->second.type() != Json::Type::kString) {
72     *error = GRPC_ERROR_CREATE("url field must be a string.");
73     return;
74   }
75   absl::StatusOr<URI> tmp_url = URI::Parse(it->second.string());
76   if (!tmp_url.ok()) {
77     *error = GRPC_ERROR_CREATE(
78         absl::StrFormat("Invalid credential source url. Error: %s",
79                         tmp_url.status().ToString()));
80     return;
81   }
82   url_ = *tmp_url;
83   // The url must follow the format of <scheme>://<authority>/<path>
84   std::vector<absl::string_view> v =
85       absl::StrSplit(it->second.string(), absl::MaxSplits('/', 3));
86   url_full_path_ = absl::StrCat("/", v[3]);
87   it = options.credential_source.object().find("headers");
88   if (it != options.credential_source.object().end()) {
89     if (it->second.type() != Json::Type::kObject) {
90       *error = GRPC_ERROR_CREATE(
91           "The JSON value of credential source headers is not an object.");
92       return;
93     }
94     for (auto const& header : it->second.object()) {
95       headers_[header.first] = header.second.string();
96     }
97   }
98   it = options.credential_source.object().find("format");
99   if (it != options.credential_source.object().end()) {
100     const Json& format_json = it->second;
101     if (format_json.type() != Json::Type::kObject) {
102       *error = GRPC_ERROR_CREATE(
103           "The JSON value of credential source format is not an object.");
104       return;
105     }
106     auto format_it = format_json.object().find("type");
107     if (format_it == format_json.object().end()) {
108       *error = GRPC_ERROR_CREATE("format.type field not present.");
109       return;
110     }
111     if (format_it->second.type() != Json::Type::kString) {
112       *error = GRPC_ERROR_CREATE("format.type field must be a string.");
113       return;
114     }
115     format_type_ = format_it->second.string();
116     if (format_type_ == "json") {
117       format_it = format_json.object().find("subject_token_field_name");
118       if (format_it == format_json.object().end()) {
119         *error = GRPC_ERROR_CREATE(
120             "format.subject_token_field_name field must be present if the "
121             "format is in Json.");
122         return;
123       }
124       if (format_it->second.type() != Json::Type::kString) {
125         *error = GRPC_ERROR_CREATE(
126             "format.subject_token_field_name field must be a string.");
127         return;
128       }
129       format_subject_token_field_name_ = format_it->second.string();
130     }
131   }
132 }
133 
RetrieveSubjectToken(HTTPRequestContext * ctx,const Options &,std::function<void (std::string,grpc_error_handle)> cb)134 void UrlExternalAccountCredentials::RetrieveSubjectToken(
135     HTTPRequestContext* ctx, const Options& /*options*/,
136     std::function<void(std::string, grpc_error_handle)> cb) {
137   if (ctx == nullptr) {
138     FinishRetrieveSubjectToken(
139         "",
140         GRPC_ERROR_CREATE(
141             "Missing HTTPRequestContext to start subject token retrieval."));
142     return;
143   }
144   auto url_for_request =
145       URI::Create(url_.scheme(), url_.authority(), url_full_path_,
146                   {} /* query params */, "" /* fragment */);
147   if (!url_for_request.ok()) {
148     FinishRetrieveSubjectToken(
149         "", absl_status_to_grpc_error(url_for_request.status()));
150     return;
151   }
152   ctx_ = ctx;
153   cb_ = cb;
154   grpc_http_request request;
155   memset(&request, 0, sizeof(grpc_http_request));
156   request.path = gpr_strdup(url_full_path_.c_str());
157   grpc_http_header* headers = nullptr;
158   request.hdr_count = headers_.size();
159   headers = static_cast<grpc_http_header*>(
160       gpr_malloc(sizeof(grpc_http_header) * request.hdr_count));
161   int i = 0;
162   for (auto const& header : headers_) {
163     headers[i].key = gpr_strdup(header.first.c_str());
164     headers[i].value = gpr_strdup(header.second.c_str());
165     ++i;
166   }
167   request.hdrs = headers;
168   grpc_http_response_destroy(&ctx_->response);
169   ctx_->response = {};
170   GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSubjectToken, this, nullptr);
171   GPR_ASSERT(http_request_ == nullptr);
172   RefCountedPtr<grpc_channel_credentials> http_request_creds;
173   if (url_.scheme() == "http") {
174     http_request_creds = RefCountedPtr<grpc_channel_credentials>(
175         grpc_insecure_credentials_create());
176   } else {
177     http_request_creds = RefCountedPtr<grpc_channel_credentials>(
178         CreateHttpRequestSSLCredentials());
179   }
180   http_request_ =
181       HttpRequest::Get(std::move(*url_for_request), nullptr /* channel args */,
182                        ctx_->pollent, &request, ctx_->deadline, &ctx_->closure,
183                        &ctx_->response, std::move(http_request_creds));
184   http_request_->Start();
185   grpc_http_request_destroy(&request);
186 }
187 
OnRetrieveSubjectToken(void * arg,grpc_error_handle error)188 void UrlExternalAccountCredentials::OnRetrieveSubjectToken(
189     void* arg, grpc_error_handle error) {
190   UrlExternalAccountCredentials* self =
191       static_cast<UrlExternalAccountCredentials*>(arg);
192   self->OnRetrieveSubjectTokenInternal(error);
193 }
194 
OnRetrieveSubjectTokenInternal(grpc_error_handle error)195 void UrlExternalAccountCredentials::OnRetrieveSubjectTokenInternal(
196     grpc_error_handle error) {
197   http_request_.reset();
198   if (!error.ok()) {
199     FinishRetrieveSubjectToken("", error);
200     return;
201   }
202   absl::string_view response_body(ctx_->response.body,
203                                   ctx_->response.body_length);
204   if (format_type_ == "json") {
205     auto response_json = JsonParse(response_body);
206     if (!response_json.ok() || response_json->type() != Json::Type::kObject) {
207       FinishRetrieveSubjectToken(
208           "", GRPC_ERROR_CREATE(
209                   "The format of response is not a valid json object."));
210       return;
211     }
212     auto response_it =
213         response_json->object().find(format_subject_token_field_name_);
214     if (response_it == response_json->object().end()) {
215       FinishRetrieveSubjectToken(
216           "", GRPC_ERROR_CREATE("Subject token field not present."));
217       return;
218     }
219     if (response_it->second.type() != Json::Type::kString) {
220       FinishRetrieveSubjectToken(
221           "", GRPC_ERROR_CREATE("Subject token field must be a string."));
222       return;
223     }
224     FinishRetrieveSubjectToken(response_it->second.string(), error);
225     return;
226   }
227   FinishRetrieveSubjectToken(std::string(response_body), absl::OkStatus());
228 }
229 
FinishRetrieveSubjectToken(std::string subject_token,grpc_error_handle error)230 void UrlExternalAccountCredentials::FinishRetrieveSubjectToken(
231     std::string subject_token, grpc_error_handle error) {
232   // Reset context
233   ctx_ = nullptr;
234   // Move object state into local variables.
235   auto cb = cb_;
236   cb_ = nullptr;
237   // Invoke the callback.
238   if (!error.ok()) {
239     cb("", error);
240   } else {
241     cb(subject_token, absl::OkStatus());
242   }
243 }
244 
245 }  // namespace grpc_core
246