1 //
2 // Copyright 2019 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
17 #include <grpc/support/port_platform.h>
18
19 #include "src/core/ext/xds/xds_bootstrap_grpc.h"
20
21 #include <stdlib.h>
22
23 #include <algorithm>
24 #include <initializer_list>
25 #include <set>
26 #include <utility>
27 #include <vector>
28
29 #include "absl/status/status.h"
30 #include "absl/status/statusor.h"
31 #include "absl/strings/match.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/str_format.h"
34 #include "absl/strings/str_join.h"
35 #include "absl/strings/string_view.h"
36 #include "absl/types/optional.h"
37
38 #include <grpc/support/json.h>
39
40 #include "src/core/lib/config/core_configuration.h"
41 #include "src/core/lib/gprpp/ref_counted_ptr.h"
42 #include "src/core/lib/json/json.h"
43 #include "src/core/lib/json/json_object_loader.h"
44 #include "src/core/lib/json/json_reader.h"
45 #include "src/core/lib/json/json_writer.h"
46 #include "src/core/lib/security/credentials/channel_creds_registry.h"
47
48 namespace grpc_core {
49
50 //
51 // GrpcXdsBootstrap::GrpcNode::Locality
52 //
53
JsonLoader(const JsonArgs &)54 const JsonLoaderInterface* GrpcXdsBootstrap::GrpcNode::Locality::JsonLoader(
55 const JsonArgs&) {
56 static const auto* loader =
57 JsonObjectLoader<Locality>()
58 .OptionalField("region", &Locality::region)
59 .OptionalField("zone", &Locality::zone)
60 .OptionalField("sub_zone", &Locality::sub_zone)
61 .Finish();
62 return loader;
63 }
64
65 //
66 // GrpcXdsBootstrap::GrpcNode
67 //
68
JsonLoader(const JsonArgs &)69 const JsonLoaderInterface* GrpcXdsBootstrap::GrpcNode::JsonLoader(
70 const JsonArgs&) {
71 static const auto* loader =
72 JsonObjectLoader<GrpcNode>()
73 .OptionalField("id", &GrpcNode::id_)
74 .OptionalField("cluster", &GrpcNode::cluster_)
75 .OptionalField("locality", &GrpcNode::locality_)
76 .OptionalField("metadata", &GrpcNode::metadata_)
77 .Finish();
78 return loader;
79 }
80
81 //
82 // GrpcXdsBootstrap::GrpcXdsServer::ChannelCreds
83 //
84
85 const JsonLoaderInterface*
JsonLoader(const JsonArgs &)86 GrpcXdsBootstrap::GrpcXdsServer::ChannelCreds::JsonLoader(const JsonArgs&) {
87 static const auto* loader =
88 JsonObjectLoader<ChannelCreds>()
89 .Field("type", &ChannelCreds::type)
90 .OptionalField("config", &ChannelCreds::config)
91 .Finish();
92 return loader;
93 }
94
95 //
96 // GrpcXdsBootstrap::GrpcXdsServer
97 //
98
99 namespace {
100
101 constexpr absl::string_view kServerFeatureIgnoreResourceDeletion =
102 "ignore_resource_deletion";
103
104 } // namespace
105
IgnoreResourceDeletion() const106 bool GrpcXdsBootstrap::GrpcXdsServer::IgnoreResourceDeletion() const {
107 return server_features_.find(std::string(
108 kServerFeatureIgnoreResourceDeletion)) != server_features_.end();
109 }
110
Equals(const XdsServer & other) const111 bool GrpcXdsBootstrap::GrpcXdsServer::Equals(const XdsServer& other) const {
112 const auto& o = static_cast<const GrpcXdsServer&>(other);
113 return (server_uri_ == o.server_uri_ &&
114 channel_creds_.type == o.channel_creds_.type &&
115 channel_creds_.config == o.channel_creds_.config &&
116 server_features_ == o.server_features_);
117 }
118
JsonLoader(const JsonArgs &)119 const JsonLoaderInterface* GrpcXdsBootstrap::GrpcXdsServer::JsonLoader(
120 const JsonArgs&) {
121 static const auto* loader =
122 JsonObjectLoader<GrpcXdsServer>()
123 .Field("server_uri", &GrpcXdsServer::server_uri_)
124 .Finish();
125 return loader;
126 }
127
JsonPostLoad(const Json & json,const JsonArgs & args,ValidationErrors * errors)128 void GrpcXdsBootstrap::GrpcXdsServer::JsonPostLoad(const Json& json,
129 const JsonArgs& args,
130 ValidationErrors* errors) {
131 // Parse "channel_creds".
132 auto channel_creds_list = LoadJsonObjectField<std::vector<ChannelCreds>>(
133 json.object(), args, "channel_creds", errors);
134 if (channel_creds_list.has_value()) {
135 ValidationErrors::ScopedField field(errors, ".channel_creds");
136 for (size_t i = 0; i < channel_creds_list->size(); ++i) {
137 ValidationErrors::ScopedField field(errors, absl::StrCat("[", i, "]"));
138 auto& creds = (*channel_creds_list)[i];
139 // Select the first channel creds type that we support.
140 if (channel_creds_.type.empty() &&
141 CoreConfiguration::Get().channel_creds_registry().IsSupported(
142 creds.type)) {
143 if (!CoreConfiguration::Get().channel_creds_registry().IsValidConfig(
144 creds.type, Json::FromObject(creds.config))) {
145 errors->AddError(absl::StrCat(
146 "invalid config for channel creds type \"", creds.type, "\""));
147 continue;
148 }
149 channel_creds_.type = std::move(creds.type);
150 channel_creds_.config = std::move(creds.config);
151 }
152 }
153 if (channel_creds_.type.empty()) {
154 errors->AddError("no known creds type found");
155 }
156 }
157 // Parse "server_features".
158 {
159 ValidationErrors::ScopedField field(errors, ".server_features");
160 auto it = json.object().find("server_features");
161 if (it != json.object().end()) {
162 if (it->second.type() != Json::Type::kArray) {
163 errors->AddError("is not an array");
164 } else {
165 const Json::Array& array = it->second.array();
166 for (const Json& feature_json : array) {
167 if (feature_json.type() == Json::Type::kString &&
168 (feature_json.string() == kServerFeatureIgnoreResourceDeletion)) {
169 server_features_.insert(feature_json.string());
170 }
171 }
172 }
173 }
174 }
175 }
176
ToJson() const177 Json GrpcXdsBootstrap::GrpcXdsServer::ToJson() const {
178 Json::Object channel_creds_json{
179 {"type", Json::FromString(channel_creds_.type)},
180 };
181 if (!channel_creds_.config.empty()) {
182 channel_creds_json["config"] = Json::FromObject(channel_creds_.config);
183 }
184 Json::Object json{
185 {"server_uri", Json::FromString(server_uri_)},
186 {"channel_creds",
187 Json::FromArray({Json::FromObject(std::move(channel_creds_json))})},
188 };
189 if (!server_features_.empty()) {
190 Json::Array server_features_json;
191 for (auto& feature : server_features_) {
192 server_features_json.emplace_back(Json::FromString(feature));
193 }
194 json["server_features"] = Json::FromArray(std::move(server_features_json));
195 }
196 return Json::FromObject(std::move(json));
197 }
198
199 //
200 // GrpcXdsBootstrap::GrpcAuthority
201 //
202
JsonLoader(const JsonArgs &)203 const JsonLoaderInterface* GrpcXdsBootstrap::GrpcAuthority::JsonLoader(
204 const JsonArgs&) {
205 static const auto* loader =
206 JsonObjectLoader<GrpcAuthority>()
207 .OptionalField(
208 "client_listener_resource_name_template",
209 &GrpcAuthority::client_listener_resource_name_template_)
210 .OptionalField("xds_servers", &GrpcAuthority::servers_)
211 .Finish();
212 return loader;
213 }
214
215 //
216 // GrpcXdsBootstrap
217 //
218
Create(absl::string_view json_string)219 absl::StatusOr<std::unique_ptr<GrpcXdsBootstrap>> GrpcXdsBootstrap::Create(
220 absl::string_view json_string) {
221 auto json = JsonParse(json_string);
222 if (!json.ok()) {
223 return absl::InvalidArgumentError(absl::StrCat(
224 "Failed to parse bootstrap JSON string: ", json.status().ToString()));
225 }
226 // Validate JSON.
227 class XdsJsonArgs : public JsonArgs {
228 public:
229 bool IsEnabled(absl::string_view key) const override {
230 if (key == "federation") return XdsFederationEnabled();
231 return true;
232 }
233 };
234 auto bootstrap = LoadFromJson<GrpcXdsBootstrap>(*json, XdsJsonArgs());
235 if (!bootstrap.ok()) return bootstrap.status();
236 return std::make_unique<GrpcXdsBootstrap>(std::move(*bootstrap));
237 }
238
JsonLoader(const JsonArgs &)239 const JsonLoaderInterface* GrpcXdsBootstrap::JsonLoader(const JsonArgs&) {
240 static const auto* loader =
241 JsonObjectLoader<GrpcXdsBootstrap>()
242 .Field("xds_servers", &GrpcXdsBootstrap::servers_)
243 .OptionalField("node", &GrpcXdsBootstrap::node_)
244 .OptionalField("certificate_providers",
245 &GrpcXdsBootstrap::certificate_providers_)
246 .OptionalField(
247 "server_listener_resource_name_template",
248 &GrpcXdsBootstrap::server_listener_resource_name_template_)
249 .OptionalField("authorities", &GrpcXdsBootstrap::authorities_,
250 "federation")
251 .OptionalField("client_default_listener_resource_name_template",
252 &GrpcXdsBootstrap::
253 client_default_listener_resource_name_template_,
254 "federation")
255 .Finish();
256 return loader;
257 }
258
JsonPostLoad(const Json &,const JsonArgs &,ValidationErrors * errors)259 void GrpcXdsBootstrap::JsonPostLoad(const Json& /*json*/,
260 const JsonArgs& /*args*/,
261 ValidationErrors* errors) {
262 // Verify that there is at least one server present.
263 {
264 ValidationErrors::ScopedField field(errors, ".xds_servers");
265 if (servers_.empty() && !errors->FieldHasErrors()) {
266 errors->AddError("must be non-empty");
267 }
268 }
269 // Verify that each authority has the right prefix in the
270 // client_listener_resource_name_template field.
271 {
272 ValidationErrors::ScopedField field(errors, ".authorities");
273 for (const auto& p : authorities_) {
274 const std::string& name = p.first;
275 const GrpcAuthority& authority =
276 static_cast<const GrpcAuthority&>(p.second);
277 ValidationErrors::ScopedField field(
278 errors, absl::StrCat("[\"", name,
279 "\"].client_listener_resource_name_template"));
280 std::string expected_prefix = absl::StrCat("xdstp://", name, "/");
281 if (!authority.client_listener_resource_name_template().empty() &&
282 !absl::StartsWith(authority.client_listener_resource_name_template(),
283 expected_prefix)) {
284 errors->AddError(
285 absl::StrCat("field must begin with \"", expected_prefix, "\""));
286 }
287 }
288 }
289 }
290
ToString() const291 std::string GrpcXdsBootstrap::ToString() const {
292 std::vector<std::string> parts;
293 if (node_.has_value()) {
294 parts.push_back(
295 absl::StrFormat("node={\n"
296 " id=\"%s\",\n"
297 " cluster=\"%s\",\n"
298 " locality={\n"
299 " region=\"%s\",\n"
300 " zone=\"%s\",\n"
301 " sub_zone=\"%s\"\n"
302 " },\n"
303 " metadata=%s,\n"
304 "},\n",
305 node_->id(), node_->cluster(), node_->locality_region(),
306 node_->locality_zone(), node_->locality_sub_zone(),
307 JsonDump(Json::FromObject(node_->metadata()))));
308 }
309 parts.push_back(
310 absl::StrFormat("servers=[\n%s\n],\n", JsonDump(servers_[0].ToJson())));
311 if (!client_default_listener_resource_name_template_.empty()) {
312 parts.push_back(absl::StrFormat(
313 "client_default_listener_resource_name_template=\"%s\",\n",
314 client_default_listener_resource_name_template_));
315 }
316 if (!server_listener_resource_name_template_.empty()) {
317 parts.push_back(
318 absl::StrFormat("server_listener_resource_name_template=\"%s\",\n",
319 server_listener_resource_name_template_));
320 }
321 parts.push_back("authorities={\n");
322 for (const auto& entry : authorities_) {
323 parts.push_back(absl::StrFormat(" %s={\n", entry.first));
324 parts.push_back(
325 absl::StrFormat(" client_listener_resource_name_template=\"%s\",\n",
326 entry.second.client_listener_resource_name_template()));
327 if (entry.second.server() != nullptr) {
328 parts.push_back(absl::StrFormat(
329 " servers=[\n%s\n],\n",
330 JsonDump(static_cast<const GrpcXdsServer*>(entry.second.server())
331 ->ToJson())));
332 }
333 parts.push_back(" },\n");
334 }
335 parts.push_back("}\n");
336 parts.push_back("certificate_providers={\n");
337 for (const auto& entry : certificate_providers_) {
338 parts.push_back(
339 absl::StrFormat(" %s={\n"
340 " plugin_name=%s\n"
341 " config=%s\n"
342 " },\n",
343 entry.first, entry.second.plugin_name,
344 entry.second.config->ToString()));
345 }
346 parts.push_back("}");
347 return absl::StrJoin(parts, "");
348 }
349
LookupAuthority(const std::string & name) const350 const XdsBootstrap::Authority* GrpcXdsBootstrap::LookupAuthority(
351 const std::string& name) const {
352 auto it = authorities_.find(name);
353 if (it != authorities_.end()) {
354 return &it->second;
355 }
356 return nullptr;
357 }
358
FindXdsServer(const XdsBootstrap::XdsServer & server) const359 const XdsBootstrap::XdsServer* GrpcXdsBootstrap::FindXdsServer(
360 const XdsBootstrap::XdsServer& server) const {
361 if (static_cast<const GrpcXdsServer&>(server) == servers_[0]) {
362 return &servers_[0];
363 }
364 for (auto& p : authorities_) {
365 const auto* authority_server =
366 static_cast<const GrpcXdsServer*>(p.second.server());
367 if (authority_server != nullptr && *authority_server == server) {
368 return authority_server;
369 }
370 }
371 return nullptr;
372 }
373
374 } // namespace grpc_core
375