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/file_external_account_credentials.h"
17
18 #include <grpc/slice.h>
19 #include <grpc/support/json.h>
20 #include <grpc/support/port_platform.h>
21
22 #include <map>
23 #include <utility>
24
25 #include "absl/status/status.h"
26 #include "absl/status/statusor.h"
27 #include "absl/strings/string_view.h"
28 #include "src/core/lib/slice/slice.h"
29 #include "src/core/lib/slice/slice_internal.h"
30 #include "src/core/util/json/json.h"
31 #include "src/core/util/json/json_reader.h"
32 #include "src/core/util/load_file.h"
33
34 namespace grpc_core {
35
36 //
37 // FileExternalAccountCredentials::FileFetchBody
38 //
39
FileFetchBody(absl::AnyInvocable<void (absl::StatusOr<std::string>)> on_done,FileExternalAccountCredentials * creds)40 FileExternalAccountCredentials::FileFetchBody::FileFetchBody(
41 absl::AnyInvocable<void(absl::StatusOr<std::string>)> on_done,
42 FileExternalAccountCredentials* creds)
43 : FetchBody(std::move(on_done)), creds_(creds) {
44 // Start work asynchronously, since we can't invoke the callback
45 // synchronously without causing a deadlock.
46 creds->event_engine().Run([self = RefAsSubclass<FileFetchBody>()]() mutable {
47 ApplicationCallbackExecCtx application_exec_ctx;
48 ExecCtx exec_ctx;
49 self->ReadFile();
50 self.reset();
51 });
52 }
53
ReadFile()54 void FileExternalAccountCredentials::FileFetchBody::ReadFile() {
55 // To retrieve the subject token, we read the file every time we make a
56 // request because it may have changed since the last request.
57 auto content_slice = LoadFile(creds_->file_, /*add_null_terminator=*/false);
58 if (!content_slice.ok()) {
59 Finish(content_slice.status());
60 return;
61 }
62 absl::string_view content = content_slice->as_string_view();
63 if (creds_->format_type_ == "json") {
64 auto content_json = JsonParse(content);
65 if (!content_json.ok() || content_json->type() != Json::Type::kObject) {
66 Finish(GRPC_ERROR_CREATE(
67 "The content of the file is not a valid json object."));
68 return;
69 }
70 auto content_it =
71 content_json->object().find(creds_->format_subject_token_field_name_);
72 if (content_it == content_json->object().end()) {
73 Finish(GRPC_ERROR_CREATE("Subject token field not present."));
74 return;
75 }
76 if (content_it->second.type() != Json::Type::kString) {
77 Finish(GRPC_ERROR_CREATE("Subject token field must be a string."));
78 return;
79 }
80 Finish(content_it->second.string());
81 return;
82 }
83 Finish(std::string(content));
84 }
85
86 //
87 // FileExternalAccountCredentials
88 //
89
90 absl::StatusOr<RefCountedPtr<FileExternalAccountCredentials>>
Create(Options options,std::vector<std::string> scopes,std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine)91 FileExternalAccountCredentials::Create(
92 Options options, std::vector<std::string> scopes,
93 std::shared_ptr<grpc_event_engine::experimental::EventEngine>
94 event_engine) {
95 grpc_error_handle error;
96 auto creds = MakeRefCounted<FileExternalAccountCredentials>(
97 std::move(options), std::move(scopes), std::move(event_engine), &error);
98 if (!error.ok()) return error;
99 return creds;
100 }
101
FileExternalAccountCredentials(Options options,std::vector<std::string> scopes,std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,grpc_error_handle * error)102 FileExternalAccountCredentials::FileExternalAccountCredentials(
103 Options options, std::vector<std::string> scopes,
104 std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine,
105 grpc_error_handle* error)
106 : ExternalAccountCredentials(options, std::move(scopes),
107 std::move(event_engine)) {
108 auto it = options.credential_source.object().find("file");
109 if (it == options.credential_source.object().end()) {
110 *error = GRPC_ERROR_CREATE("file field not present.");
111 return;
112 }
113 if (it->second.type() != Json::Type::kString) {
114 *error = GRPC_ERROR_CREATE("file field must be a string.");
115 return;
116 }
117 file_ = it->second.string();
118 it = options.credential_source.object().find("format");
119 if (it != options.credential_source.object().end()) {
120 const Json& format_json = it->second;
121 if (format_json.type() != Json::Type::kObject) {
122 *error = GRPC_ERROR_CREATE(
123 "The JSON value of credential source format is not an object.");
124 return;
125 }
126 auto format_it = format_json.object().find("type");
127 if (format_it == format_json.object().end()) {
128 *error = GRPC_ERROR_CREATE("format.type field not present.");
129 return;
130 }
131 if (format_it->second.type() != Json::Type::kString) {
132 *error = GRPC_ERROR_CREATE("format.type field must be a string.");
133 return;
134 }
135 format_type_ = format_it->second.string();
136 if (format_type_ == "json") {
137 format_it = format_json.object().find("subject_token_field_name");
138 if (format_it == format_json.object().end()) {
139 *error = GRPC_ERROR_CREATE(
140 "format.subject_token_field_name field must be present if the "
141 "format is in Json.");
142 return;
143 }
144 if (format_it->second.type() != Json::Type::kString) {
145 *error = GRPC_ERROR_CREATE(
146 "format.subject_token_field_name field must be a string.");
147 return;
148 }
149 format_subject_token_field_name_ = format_it->second.string();
150 }
151 }
152 }
153
debug_string()154 std::string FileExternalAccountCredentials::debug_string() {
155 return absl::StrCat("FileExternalAccountCredentials{Audience:", audience(),
156 ")");
157 }
158
Type()159 UniqueTypeName FileExternalAccountCredentials::Type() {
160 static UniqueTypeName::Factory kFactory("FileExternalAccountCredentials");
161 return kFactory.Create();
162 }
163
164 OrphanablePtr<ExternalAccountCredentials::FetchBody>
RetrieveSubjectToken(Timestamp,absl::AnyInvocable<void (absl::StatusOr<std::string>)> on_done)165 FileExternalAccountCredentials::RetrieveSubjectToken(
166 Timestamp /*deadline*/,
167 absl::AnyInvocable<void(absl::StatusOr<std::string>)> on_done) {
168 return MakeOrphanable<FileFetchBody>(std::move(on_done), this);
169 }
170
CredentialSourceType()171 absl::string_view FileExternalAccountCredentials::CredentialSourceType() {
172 return "file";
173 }
174
175 } // namespace grpc_core
176