1
#include "source/common/config/utility.h"
2

            
3
#include "envoy/config/bootstrap/v3/bootstrap.pb.h"
4
#include "envoy/config/cluster/v3/cluster.pb.h"
5
#include "envoy/config/core/v3/address.pb.h"
6
#include "envoy/config/core/v3/config_source.pb.h"
7
#include "envoy/config/core/v3/grpc_service.pb.h"
8
#include "envoy/config/endpoint/v3/endpoint.pb.h"
9
#include "envoy/config/endpoint/v3/endpoint_components.pb.h"
10
#include "envoy/grpc/async_client_manager.h"
11
#include "envoy/stats/scope.h"
12

            
13
#include "source/common/common/assert.h"
14
#include "source/common/protobuf/utility.h"
15

            
16
#include "absl/status/status.h"
17
#include "absl/types/optional.h"
18

            
19
namespace Envoy {
20
namespace Config {
21

            
22
190
std::string Utility::truncateGrpcStatusMessage(absl::string_view error_message) {
23
  // GRPC sends error message via trailers, which by default has a 8KB size limit(see
24
  // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests). Truncates the
25
  // error message if it's too long.
26
190
  constexpr uint32_t kProtobufErrMsgLen = 4096;
27
190
  return fmt::format("{}{}", error_message.substr(0, kProtobufErrMsgLen),
28
190
                     error_message.length() > kProtobufErrMsgLen ? "...(truncated)" : "");
29
190
}
30

            
31
absl::StatusOr<Upstream::ClusterConstOptRef> Utility::checkCluster(absl::string_view error_prefix,
32
                                                                   absl::string_view cluster_name,
33
                                                                   Upstream::ClusterManager& cm,
34
105
                                                                   bool allow_added_via_api) {
35
105
  const auto cluster = cm.getActiveOrWarmingCluster(std::string(cluster_name));
36
105
  if (!cluster) {
37
7
    return absl::InvalidArgumentError(
38
7
        fmt::format("{}: unknown cluster '{}'", error_prefix, cluster_name));
39
7
  }
40

            
41
98
  if (!allow_added_via_api && cluster->info()->addedViaApi()) {
42
1
    return absl::InvalidArgumentError(fmt::format(
43
1
        "{}: invalid cluster '{}': currently only static (non-CDS) clusters are supported",
44
1
        error_prefix, cluster_name));
45
1
  }
46
97
  return Upstream::ClusterConstOptRef(*cluster);
47
98
}
48

            
49
absl::Status Utility::checkLocalInfo(absl::string_view error_prefix,
50
14484
                                     const LocalInfo::LocalInfo& local_info) {
51
14484
  if (local_info.clusterName().empty() || local_info.nodeName().empty()) {
52
9
    return absl::InvalidArgumentError(
53
9
        fmt::format("{}: node 'id' and 'cluster' are required. Set it either in 'node' config or "
54
9
                    "via --service-node and --service-cluster options.",
55
9
                    error_prefix, local_info.node().DebugString()));
56
9
  }
57
14475
  return absl::OkStatus();
58
14484
}
59

            
60
absl::Status Utility::checkFilesystemSubscriptionBackingPath(const std::string& path,
61
10197
                                                             Api::Api& api) {
62
  // TODO(junr03): the file might be deleted between this check and the
63
  // watch addition.
64
10197
  if (!api.fileSystem().fileExists(path)) {
65
3
    return absl::InvalidArgumentError(fmt::format(
66
3
        "paths must refer to an existing path in the system: '{}' does not exist", path));
67
3
  }
68
10194
  return absl::OkStatus();
69
10197
}
70

            
71
namespace {
72
/**
73
 * Check the grpc_services and cluster_names for API config sanity. Throws on error.
74
 * @param api_config_source the config source to validate.
75
 * @param max_grpc_services the maximal number of grpc services allowed.
76
 * @return an invalid status when an API config has the wrong number of gRPC
77
 * services or cluster names, depending on expectations set by its API type.
78
 */
79
absl::Status
80
checkApiConfigSourceNames(const envoy::config::core::v3::ApiConfigSource& api_config_source,
81
2962
                          int max_grpc_services) {
82
2962
  const bool is_grpc =
83
2962
      (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC ||
84
2962
       api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC ||
85
2962
       api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC ||
86
2962
       api_config_source.api_type() ==
87
95
           envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC);
88

            
89
2962
  if (api_config_source.cluster_names().empty() && api_config_source.grpc_services().empty()) {
90
8
    return absl::InvalidArgumentError(
91
8
        fmt::format("API configs must have either a gRPC service or a cluster name defined: {}",
92
8
                    api_config_source.DebugString()));
93
8
  }
94

            
95
2954
  if (is_grpc) {
96
2942
    if (!api_config_source.cluster_names().empty()) {
97
5
      return absl::InvalidArgumentError(
98
5
          fmt::format("{}::(AGGREGATED_)(DELTA_)GRPC must not have a cluster name specified: {}",
99
5
                      api_config_source.GetTypeName(), api_config_source.DebugString()));
100
5
    }
101
2937
    if (api_config_source.grpc_services_size() > max_grpc_services) {
102
7
      return absl::InvalidArgumentError(fmt::format(
103
7
          "{}::(AGGREGATED_)(DELTA_)GRPC must have no more than {} gRPC services specified: {}",
104
7
          api_config_source.GetTypeName(), max_grpc_services, api_config_source.DebugString()));
105
7
    }
106
2937
  } else {
107
12
    if (!api_config_source.grpc_services().empty()) {
108
2
      return absl::InvalidArgumentError(
109
2
          fmt::format("{}, if not a gRPC type, must not have a gRPC service specified: {}",
110
2
                      api_config_source.GetTypeName(), api_config_source.DebugString()));
111
2
    }
112
10
    if (api_config_source.cluster_names().size() != 1) {
113
1
      return absl::InvalidArgumentError(
114
1
          fmt::format("{} must have a singleton cluster name specified: {}",
115
1
                      api_config_source.GetTypeName(), api_config_source.DebugString()));
116
1
    }
117
10
  }
118
2939
  return absl::OkStatus();
119
2954
}
120
} // namespace
121

            
122
absl::Status
123
Utility::validateClusterName(const Upstream::ClusterManager::ClusterSet& primary_clusters,
124
537
                             absl::string_view cluster_name, absl::string_view config_source) {
125
537
  const auto& it = primary_clusters.find(cluster_name);
126
537
  if (it == primary_clusters.end()) {
127
5
    return absl::InvalidArgumentError(
128
5
        fmt::format("{} must have a statically defined non-EDS cluster: '{}' does "
129
5
                    "not exist, was added via api, or is an EDS cluster",
130
5
                    config_source, cluster_name));
131
5
  }
132
532
  return absl::OkStatus();
133
537
}
134

            
135
absl::Status Utility::checkApiConfigSourceSubscriptionBackingCluster(
136
    const Upstream::ClusterManager::ClusterSet& primary_clusters,
137
1121
    const envoy::config::core::v3::ApiConfigSource& api_config_source) {
138
  // We don't need to check backing sources for ADS sources, the backing cluster must be verified in
139
  // the ads_config.
140
1121
  if (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC ||
141
1121
      api_config_source.api_type() ==
142
1114
          envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC) {
143
34
    return absl::OkStatus();
144
34
  }
145
1087
  RETURN_IF_NOT_OK(checkApiConfigSourceNames(
146
1087
      api_config_source,
147
1087
      Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") ? 2 : 1));
148

            
149
1077
  const bool is_grpc =
150
1077
      (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC);
151

            
152
1077
  if (!api_config_source.cluster_names().empty()) {
153
    // All API configs of type REST and UNSUPPORTED_REST_LEGACY should have cluster names.
154
    // Additionally, some gRPC API configs might have a cluster name set instead
155
    // of an envoy gRPC.
156
7
    RETURN_IF_NOT_OK(Utility::validateClusterName(
157
7
        primary_clusters, api_config_source.cluster_names()[0], api_config_source.GetTypeName()));
158
1070
  } else if (is_grpc) {
159
    // Some ApiConfigSources of type GRPC won't have a cluster name, such as if
160
    // they've been configured with google_grpc.
161
832
    if (api_config_source.grpc_services()[0].has_envoy_grpc()) {
162
      // If an Envoy gRPC exists, we take its cluster name.
163
528
      RETURN_IF_NOT_OK(Utility::validateClusterName(
164
528
          primary_clusters, api_config_source.grpc_services()[0].envoy_grpc().cluster_name(),
165
528
          api_config_source.GetTypeName()));
166
525
    }
167
829
    if (Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") &&
168
829
        api_config_source.grpc_services_size() == 2 &&
169
829
        api_config_source.grpc_services()[1].has_envoy_grpc()) {
170
      // If an Envoy failover gRPC exists, we validate its cluster name.
171
2
      RETURN_IF_NOT_OK(Utility::validateClusterName(
172
2
          primary_clusters, api_config_source.grpc_services()[1].envoy_grpc().cluster_name(),
173
2
          api_config_source.GetTypeName()));
174
2
    }
175
829
  }
176
  // Otherwise, there is no cluster name to validate.
177
1072
  return absl::OkStatus();
178
1077
}
179

            
180
absl::optional<std::string>
181
1049
Utility::getGrpcControlPlane(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
182
1049
  if (api_config_source.grpc_services_size() > 0) {
183
1048
    std::string res = "";
184
    // In case more than one grpc service is defined, concatenate the names for
185
    // a unique GrpcControlPlane identifier.
186
1048
    if (api_config_source.grpc_services(0).has_envoy_grpc()) {
187
639
      res = api_config_source.grpc_services(0).envoy_grpc().cluster_name();
188
668
    } else if (api_config_source.grpc_services(0).has_google_grpc()) {
189
409
      res = api_config_source.grpc_services(0).google_grpc().target_uri();
190
409
    }
191
    // Concatenate the failover gRPC service.
192
1048
    if (api_config_source.grpc_services_size() == 2) {
193
14
      if (api_config_source.grpc_services(1).has_envoy_grpc()) {
194
11
        absl::StrAppend(&res, ",", api_config_source.grpc_services(1).envoy_grpc().cluster_name());
195
11
      } else if (api_config_source.grpc_services(1).has_google_grpc()) {
196
3
        absl::StrAppend(&res, ",", api_config_source.grpc_services(1).google_grpc().target_uri());
197
3
      }
198
14
    }
199
1048
    return res;
200
1048
  }
201
1
  return absl::nullopt;
202
1049
}
203

            
204
std::chrono::milliseconds Utility::configSourceInitialFetchTimeout(
205
2594
    const envoy::config::core::v3::ConfigSource& config_source) {
206
2594
  return std::chrono::milliseconds(
207
2594
      PROTOBUF_GET_MS_OR_DEFAULT(config_source, initial_fetch_timeout, 15000));
208
2594
}
209

            
210
absl::StatusOr<RateLimitSettings>
211
1682
Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
212
1682
  RateLimitSettings rate_limit_settings;
213
1682
  if (api_config_source.has_rate_limit_settings()) {
214
20
    rate_limit_settings.enabled_ = true;
215
20
    rate_limit_settings.max_tokens_ =
216
20
        PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), max_tokens,
217
20
                                        Envoy::Config::RateLimitSettings::DefaultMaxTokens);
218
20
    rate_limit_settings.fill_rate_ =
219
20
        PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), fill_rate,
220
20
                                        Envoy::Config::RateLimitSettings::DefaultFillRate);
221
    // Reject the NaN and Inf values.
222
20
    if (std::isnan(rate_limit_settings.fill_rate_) || std::isinf(rate_limit_settings.fill_rate_)) {
223
14
      return absl::InvalidArgumentError(
224
14
          fmt::format("The value of fill_rate in RateLimitSettings ({}) must not be NaN nor Inf",
225
14
                      rate_limit_settings.fill_rate_));
226
14
    }
227
20
  }
228
1668
  return rate_limit_settings;
229
1682
}
230

            
231
namespace {
232
// Returns true iff the api_type is AGGREGATED_GRPC or AGGREGATED_DELTA_GRPC.
233
141
bool isApiTypeAggregated(const envoy::config::core::v3::ApiConfigSource::ApiType api_type) {
234
141
  return (api_type == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC) ||
235
141
         (api_type == envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC);
236
141
}
237

            
238
// Returns true iff the api_type is GRPC or DELTA_GRPC.
239
1721
bool isApiTypeNonAggregated(const envoy::config::core::v3::ApiConfigSource::ApiType api_type) {
240
1721
  return (api_type == envoy::config::core::v3::ApiConfigSource::GRPC) ||
241
1721
         (api_type == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC);
242
1721
}
243
} // namespace
244

            
245
absl::StatusOr<Envoy::OptRef<const envoy::config::core::v3::GrpcService>>
246
Utility::getGrpcConfigFromApiConfigSource(
247
    const envoy::config::core::v3::ApiConfigSource& api_config_source, int grpc_service_idx,
248
1875
    bool xdstp_config_source) {
249
1875
  RETURN_IF_NOT_OK(checkApiConfigSourceNames(
250
1875
      api_config_source,
251
1875
      Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") ? 2 : 1));
252

            
253
1862
  if (xdstp_config_source) {
254
141
    if (!isApiTypeAggregated(api_config_source.api_type())) {
255
2
      return absl::InvalidArgumentError(fmt::format("{} type must be of aggregated gRPC: {}",
256
2
                                                    api_config_source.GetTypeName(),
257
2
                                                    api_config_source.DebugString()));
258
2
    }
259
1773
  } else {
260
1721
    if (!isApiTypeNonAggregated(api_config_source.api_type())) {
261
2
      return absl::InvalidArgumentError(fmt::format("{} type must be of non-aggregated gRPC: {}",
262
2
                                                    api_config_source.GetTypeName(),
263
2
                                                    api_config_source.DebugString()));
264
2
    }
265
1721
  }
266

            
267
1858
  if (grpc_service_idx >= api_config_source.grpc_services_size()) {
268
    // No returned factory in case there's no entry.
269
12
    return absl::nullopt;
270
12
  }
271

            
272
1846
  return Envoy::makeOptRef(api_config_source.grpc_services(grpc_service_idx));
273
1858
}
274

            
275
absl::StatusOr<Grpc::AsyncClientFactoryPtr> Utility::factoryForGrpcApiConfigSource(
276
    Grpc::AsyncClientManager& async_client_manager,
277
    const envoy::config::core::v3::ApiConfigSource& api_config_source, Stats::Scope& scope,
278
1850
    bool skip_cluster_check, int grpc_service_idx, bool xdstp_config_source) {
279

            
280
1850
  absl::StatusOr<Envoy::OptRef<const envoy::config::core::v3::GrpcService>> maybe_grpc_service =
281
1850
      getGrpcConfigFromApiConfigSource(api_config_source, grpc_service_idx, xdstp_config_source);
282
1850
  RETURN_IF_NOT_OK(maybe_grpc_service.status());
283

            
284
1833
  if (!maybe_grpc_service.value().has_value()) {
285
10
    return nullptr;
286
10
  }
287
1823
  return async_client_manager.factoryForGrpcService(*maybe_grpc_service.value(), scope,
288
1823
                                                    skip_cluster_check);
289
1833
}
290

            
291
absl::Status Utility::translateOpaqueConfig(const Protobuf::Any& typed_config,
292
                                            ProtobufMessage::ValidationVisitor& validation_visitor,
293
129303
                                            Protobuf::Message& out_proto) {
294
129303
  static const std::string struct_type(Protobuf::Struct::default_instance().GetTypeName());
295
129303
  static const std::string typed_struct_type(
296
129303
      xds::type::v3::TypedStruct::default_instance().GetTypeName());
297
129303
  static const std::string legacy_typed_struct_type(
298
129303
      udpa::type::v1::TypedStruct::default_instance().GetTypeName());
299
129303
  if (!typed_config.value().empty()) {
300
    // Unpack methods will only use the fully qualified type name after the last '/'.
301
    // https://github.com/protocolbuffers/protobuf/blob/3.6.x/src/google/protobuf/any.proto#L87
302
58618
    absl::string_view type = TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url());
303

            
304
58618
    if (type == typed_struct_type) {
305
38
      xds::type::v3::TypedStruct typed_struct;
306
38
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(typed_config, typed_struct));
307
      // if out_proto is expecting Struct, return directly
308
38
      if (out_proto.GetTypeName() == struct_type) {
309
25
        out_proto.CheckTypeAndMergeFrom(typed_struct.value());
310
37
      } else {
311
        // The typed struct might match out_proto, or some earlier version, let
312
        // MessageUtil::jsonConvert sort this out.
313
13
#ifdef ENVOY_ENABLE_YAML
314
13
        MessageUtil::jsonConvert(typed_struct.value(), validation_visitor, out_proto);
315
#else
316
        IS_ENVOY_BUG("Attempting to use JSON typed structs with JSON compiled out");
317
#endif
318
13
      }
319
58591
    } else if (type == legacy_typed_struct_type) {
320
6
      udpa::type::v1::TypedStruct typed_struct;
321
6
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(typed_config, typed_struct));
322
      // if out_proto is expecting Struct, return directly
323
6
      if (out_proto.GetTypeName() == struct_type) {
324
1
        out_proto.CheckTypeAndMergeFrom(typed_struct.value());
325
5
      } else {
326
        // The typed struct might match out_proto, or some earlier version, let
327
        // MessageUtil::jsonConvert sort this out.
328
5
#ifdef ENVOY_ENABLE_YAML
329
5
        MessageUtil::jsonConvert(typed_struct.value(), validation_visitor, out_proto);
330
#else
331
        UNREFERENCED_PARAMETER(validation_visitor);
332
        IS_ENVOY_BUG("Attempting to use legacy JSON structs with JSON compiled out");
333
#endif
334
5
      }
335
6
    } // out_proto is expecting Struct, unpack directly
336
58574
    else if (type != struct_type || out_proto.GetTypeName() == struct_type) {
337
58537
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(typed_config, out_proto));
338
58560
    } else {
339
37
#ifdef ENVOY_ENABLE_YAML
340
37
      Protobuf::Struct struct_config;
341
37
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(typed_config, struct_config));
342
37
      MessageUtil::jsonConvert(struct_config, validation_visitor, out_proto);
343
#else
344
      IS_ENVOY_BUG("Attempting to use JSON structs with JSON compiled out");
345
#endif
346
37
    }
347
58618
  }
348
129301
  return absl::OkStatus();
349
129303
}
350

            
351
absl::StatusOr<JitteredExponentialBackOffStrategyPtr>
352
Utility::buildJitteredExponentialBackOffStrategy(
353
    absl::optional<const envoy::config::core::v3::BackoffStrategy> backoff,
354
    Random::RandomGenerator& random, const uint32_t default_base_interval_ms,
355
1762
    absl::optional<const uint32_t> default_max_interval_ms) {
356
  // BackoffStrategy config is specified
357
1762
  if (backoff != absl::nullopt) {
358
54
    uint32_t base_interval_ms = PROTOBUF_GET_MS_REQUIRED(backoff.value(), base_interval);
359
54
    uint32_t max_interval_ms =
360
54
        PROTOBUF_GET_MS_OR_DEFAULT(backoff.value(), max_interval, base_interval_ms * 10);
361

            
362
54
    if (max_interval_ms < base_interval_ms) {
363
5
      return absl::InvalidArgumentError(
364
5
          "max_interval must be greater than or equal to the base_interval");
365
5
    }
366
49
    return std::make_unique<JitteredExponentialBackOffStrategy>(base_interval_ms, max_interval_ms,
367
49
                                                                random);
368
54
  }
369

            
370
  // default_base_interval_ms must be greater than zero
371
1708
  if (default_base_interval_ms == 0) {
372
3
    return absl::InvalidArgumentError("default_base_interval_ms must be greater than zero");
373
3
  }
374

            
375
  // default maximum interval is specified
376
1705
  if (default_max_interval_ms != absl::nullopt) {
377
1702
    if (default_max_interval_ms.value() < default_base_interval_ms) {
378
1
      return absl::InvalidArgumentError(
379
1
          "default_max_interval_ms must be greater than or equal to the default_base_interval_ms");
380
1
    }
381
1701
    return std::make_unique<JitteredExponentialBackOffStrategy>(
382
1701
        default_base_interval_ms, default_max_interval_ms.value(), random);
383
1702
  }
384
  // use default base interval
385
3
  return std::make_unique<JitteredExponentialBackOffStrategy>(
386
3
      default_base_interval_ms, default_base_interval_ms * 10, random);
387
1705
}
388

            
389
absl::Status Utility::validateTerminalFilters(const std::string& name,
390
                                              const std::string& filter_type,
391
                                              const std::string& filter_chain_type,
392
                                              bool is_terminal_filter,
393
27577
                                              bool last_filter_in_current_config) {
394
27577
  if (is_terminal_filter && !last_filter_in_current_config) {
395
15
    return absl::InvalidArgumentError(
396
15
        fmt::format("Error: terminal filter named {} of type {} must be the "
397
15
                    "last filter in a {} filter chain.",
398
15
                    name, filter_type, filter_chain_type));
399
27562
  } else if (!is_terminal_filter && last_filter_in_current_config) {
400
6
    absl::string_view extra = "";
401
6
    if (filter_chain_type == "router upstream http") {
402
1
      extra = " When upstream_http_filters are specified, they must explicitly end with an "
403
1
              "UpstreamCodec filter.";
404
1
    }
405
6
    return absl::InvalidArgumentError(fmt::format("Error: non-terminal filter named {} of type "
406
6
                                                  "{} is the last filter in a {} filter chain.{}",
407
6
                                                  name, filter_type, filter_chain_type, extra));
408
6
  }
409
27556
  return absl::OkStatus();
410
27577
}
411

            
412
} // namespace Config
413
} // namespace Envoy