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