| // |
| // Copyright 2020 gRPC authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| #include "src/core/lib/security/credentials/external/url_external_account_credentials.h" |
| |
| #include <string.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_split.h" |
| #include "absl/strings/string_view.h" |
| |
| #include <grpc/credentials.h> |
| #include <grpc/grpc.h> |
| #include <grpc/grpc_security.h> |
| #include <grpc/support/alloc.h> |
| #include <grpc/support/json.h> |
| #include <grpc/support/log.h> |
| #include <grpc/support/port_platform.h> |
| #include <grpc/support/string_util.h> |
| |
| #include "src/core/lib/http/httpcli_ssl_credentials.h" |
| #include "src/core/lib/http/parser.h" |
| #include "src/core/lib/iomgr/closure.h" |
| #include "src/core/lib/json/json.h" |
| #include "src/core/lib/json/json_reader.h" |
| #include "src/core/lib/security/credentials/credentials.h" |
| #include "src/core/lib/transport/error_utils.h" |
| |
| namespace grpc_core { |
| |
| RefCountedPtr<UrlExternalAccountCredentials> |
| UrlExternalAccountCredentials::Create(Options options, |
| std::vector<std::string> scopes, |
| grpc_error_handle* error) { |
| auto creds = MakeRefCounted<UrlExternalAccountCredentials>( |
| std::move(options), std::move(scopes), error); |
| if (error->ok()) { |
| return creds; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| UrlExternalAccountCredentials::UrlExternalAccountCredentials( |
| Options options, std::vector<std::string> scopes, grpc_error_handle* error) |
| : ExternalAccountCredentials(options, std::move(scopes)) { |
| auto it = options.credential_source.object().find("url"); |
| if (it == options.credential_source.object().end()) { |
| *error = GRPC_ERROR_CREATE("url field not present."); |
| return; |
| } |
| if (it->second.type() != Json::Type::kString) { |
| *error = GRPC_ERROR_CREATE("url field must be a string."); |
| return; |
| } |
| absl::StatusOr<URI> tmp_url = URI::Parse(it->second.string()); |
| if (!tmp_url.ok()) { |
| *error = GRPC_ERROR_CREATE( |
| absl::StrFormat("Invalid credential source url. Error: %s", |
| tmp_url.status().ToString())); |
| return; |
| } |
| url_ = *tmp_url; |
| // The url must follow the format of <scheme>://<authority>/<path> |
| std::vector<absl::string_view> v = |
| absl::StrSplit(it->second.string(), absl::MaxSplits('/', 3)); |
| url_full_path_ = absl::StrCat("/", v[3]); |
| it = options.credential_source.object().find("headers"); |
| if (it != options.credential_source.object().end()) { |
| if (it->second.type() != Json::Type::kObject) { |
| *error = GRPC_ERROR_CREATE( |
| "The JSON value of credential source headers is not an object."); |
| return; |
| } |
| for (auto const& header : it->second.object()) { |
| headers_[header.first] = header.second.string(); |
| } |
| } |
| it = options.credential_source.object().find("format"); |
| if (it != options.credential_source.object().end()) { |
| const Json& format_json = it->second; |
| if (format_json.type() != Json::Type::kObject) { |
| *error = GRPC_ERROR_CREATE( |
| "The JSON value of credential source format is not an object."); |
| return; |
| } |
| auto format_it = format_json.object().find("type"); |
| if (format_it == format_json.object().end()) { |
| *error = GRPC_ERROR_CREATE("format.type field not present."); |
| return; |
| } |
| if (format_it->second.type() != Json::Type::kString) { |
| *error = GRPC_ERROR_CREATE("format.type field must be a string."); |
| return; |
| } |
| format_type_ = format_it->second.string(); |
| if (format_type_ == "json") { |
| format_it = format_json.object().find("subject_token_field_name"); |
| if (format_it == format_json.object().end()) { |
| *error = GRPC_ERROR_CREATE( |
| "format.subject_token_field_name field must be present if the " |
| "format is in Json."); |
| return; |
| } |
| if (format_it->second.type() != Json::Type::kString) { |
| *error = GRPC_ERROR_CREATE( |
| "format.subject_token_field_name field must be a string."); |
| return; |
| } |
| format_subject_token_field_name_ = format_it->second.string(); |
| } |
| } |
| } |
| |
| void UrlExternalAccountCredentials::RetrieveSubjectToken( |
| HTTPRequestContext* ctx, const Options& /*options*/, |
| std::function<void(std::string, grpc_error_handle)> cb) { |
| if (ctx == nullptr) { |
| FinishRetrieveSubjectToken( |
| "", |
| GRPC_ERROR_CREATE( |
| "Missing HTTPRequestContext to start subject token retrieval.")); |
| return; |
| } |
| auto url_for_request = |
| URI::Create(url_.scheme(), url_.authority(), url_full_path_, |
| {} /* query params */, "" /* fragment */); |
| if (!url_for_request.ok()) { |
| FinishRetrieveSubjectToken( |
| "", absl_status_to_grpc_error(url_for_request.status())); |
| return; |
| } |
| ctx_ = ctx; |
| cb_ = cb; |
| grpc_http_request request; |
| memset(&request, 0, sizeof(grpc_http_request)); |
| request.path = gpr_strdup(url_full_path_.c_str()); |
| grpc_http_header* headers = nullptr; |
| request.hdr_count = headers_.size(); |
| headers = static_cast<grpc_http_header*>( |
| gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); |
| int i = 0; |
| for (auto const& header : headers_) { |
| headers[i].key = gpr_strdup(header.first.c_str()); |
| headers[i].value = gpr_strdup(header.second.c_str()); |
| ++i; |
| } |
| request.hdrs = headers; |
| grpc_http_response_destroy(&ctx_->response); |
| ctx_->response = {}; |
| GRPC_CLOSURE_INIT(&ctx_->closure, OnRetrieveSubjectToken, this, nullptr); |
| GPR_ASSERT(http_request_ == nullptr); |
| RefCountedPtr<grpc_channel_credentials> http_request_creds; |
| if (url_.scheme() == "http") { |
| http_request_creds = RefCountedPtr<grpc_channel_credentials>( |
| grpc_insecure_credentials_create()); |
| } else { |
| http_request_creds = CreateHttpRequestSSLCredentials(); |
| } |
| http_request_ = |
| HttpRequest::Get(std::move(*url_for_request), nullptr /* channel args */, |
| ctx_->pollent, &request, ctx_->deadline, &ctx_->closure, |
| &ctx_->response, std::move(http_request_creds)); |
| http_request_->Start(); |
| grpc_http_request_destroy(&request); |
| } |
| |
| void UrlExternalAccountCredentials::OnRetrieveSubjectToken( |
| void* arg, grpc_error_handle error) { |
| UrlExternalAccountCredentials* self = |
| static_cast<UrlExternalAccountCredentials*>(arg); |
| self->OnRetrieveSubjectTokenInternal(error); |
| } |
| |
| void UrlExternalAccountCredentials::OnRetrieveSubjectTokenInternal( |
| grpc_error_handle error) { |
| http_request_.reset(); |
| if (!error.ok()) { |
| FinishRetrieveSubjectToken("", error); |
| return; |
| } |
| absl::string_view response_body(ctx_->response.body, |
| ctx_->response.body_length); |
| if (format_type_ == "json") { |
| auto response_json = JsonParse(response_body); |
| if (!response_json.ok() || response_json->type() != Json::Type::kObject) { |
| FinishRetrieveSubjectToken( |
| "", GRPC_ERROR_CREATE( |
| "The format of response is not a valid json object.")); |
| return; |
| } |
| auto response_it = |
| response_json->object().find(format_subject_token_field_name_); |
| if (response_it == response_json->object().end()) { |
| FinishRetrieveSubjectToken( |
| "", GRPC_ERROR_CREATE("Subject token field not present.")); |
| return; |
| } |
| if (response_it->second.type() != Json::Type::kString) { |
| FinishRetrieveSubjectToken( |
| "", GRPC_ERROR_CREATE("Subject token field must be a string.")); |
| return; |
| } |
| FinishRetrieveSubjectToken(response_it->second.string(), error); |
| return; |
| } |
| FinishRetrieveSubjectToken(std::string(response_body), absl::OkStatus()); |
| } |
| |
| void UrlExternalAccountCredentials::FinishRetrieveSubjectToken( |
| std::string subject_token, grpc_error_handle error) { |
| // Reset context |
| ctx_ = nullptr; |
| // Move object state into local variables. |
| auto cb = cb_; |
| cb_ = nullptr; |
| // Invoke the callback. |
| if (!error.ok()) { |
| cb("", error); |
| } else { |
| cb(subject_token, absl::OkStatus()); |
| } |
| } |
| |
| absl::string_view UrlExternalAccountCredentials::CredentialSourceType() { |
| return "url"; |
| } |
| |
| } // namespace grpc_core |