• 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 "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