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