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
192
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
192
  constexpr uint32_t kProtobufErrMsgLen = 4096;
27
192
  return fmt::format("{}{}", error_message.substr(0, kProtobufErrMsgLen),
28
192
                     error_message.length() > kProtobufErrMsgLen ? "...(truncated)" : "");
29
192
}
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
14520
                                     const LocalInfo::LocalInfo& local_info) {
51
14520
  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
14511
  return absl::OkStatus();
58
14520
}
59

            
60
absl::Status Utility::checkFilesystemSubscriptionBackingPath(const std::string& path,
61
10160
                                                             Api::Api& api) {
62
  // TODO(junr03): the file might be deleted between this check and the
63
  // watch addition.
64
10160
  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
10157
  return absl::OkStatus();
69
10160
}
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
3044
                          int max_grpc_services) {
82
3044
  const bool is_grpc =
83
3044
      (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::GRPC ||
84
3044
       api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC ||
85
3044
       api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC ||
86
3044
       api_config_source.api_type() ==
87
95
           envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC);
88

            
89
3044
  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
3036
  if (is_grpc) {
96
3024
    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
3019
    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
3019
  } 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
3021
  return absl::OkStatus();
119
3036
}
120
} // namespace
121

            
122
absl::Status
123
Utility::validateClusterName(const Upstream::ClusterManager::ClusterSet& primary_clusters,
124
560
                             absl::string_view cluster_name, absl::string_view config_source) {
125
560
  const auto& it = primary_clusters.find(cluster_name);
126
560
  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
555
  return absl::OkStatus();
133
560
}
134

            
135
absl::Status Utility::checkApiConfigSourceSubscriptionBackingCluster(
136
    const Upstream::ClusterManager::ClusterSet& primary_clusters,
137
1170
    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
1170
  if (api_config_source.api_type() == envoy::config::core::v3::ApiConfigSource::AGGREGATED_GRPC ||
141
1170
      api_config_source.api_type() ==
142
1163
          envoy::config::core::v3::ApiConfigSource::AGGREGATED_DELTA_GRPC) {
143
34
    return absl::OkStatus();
144
34
  }
145
1136
  RETURN_IF_NOT_OK(checkApiConfigSourceNames(
146
1136
      api_config_source,
147
1136
      Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") ? 2 : 1));
148

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

            
152
1126
  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
1119
  } 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
855
    if (api_config_source.grpc_services()[0].has_envoy_grpc()) {
162
      // If an Envoy gRPC exists, we take its cluster name.
163
551
      RETURN_IF_NOT_OK(Utility::validateClusterName(
164
551
          primary_clusters, api_config_source.grpc_services()[0].envoy_grpc().cluster_name(),
165
551
          api_config_source.GetTypeName()));
166
548
    }
167
852
    if (Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") &&
168
852
        api_config_source.grpc_services_size() == 2 &&
169
852
        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
852
  }
176
  // Otherwise, there is no cluster name to validate.
177
1121
  return absl::OkStatus();
178
1126
}
179

            
180
absl::optional<std::string>
181
1072
Utility::getGrpcControlPlane(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
182
1072
  if (api_config_source.grpc_services_size() > 0) {
183
1071
    std::string res = "";
184
    // In case more than one grpc service is defined, concatenate the names for
185
    // a unique GrpcControlPlane identifier.
186
1071
    if (api_config_source.grpc_services(0).has_envoy_grpc()) {
187
662
      res = api_config_source.grpc_services(0).envoy_grpc().cluster_name();
188
684
    } 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
1071
    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
1071
    return res;
200
1071
  }
201
1
  return absl::nullopt;
202
1072
}
203

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

            
210
absl::StatusOr<RateLimitSettings>
211
1723
Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source) {
212
1723
  RateLimitSettings rate_limit_settings;
213
1723
  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
1709
  return rate_limit_settings;
229
1723
}
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
1754
bool isApiTypeNonAggregated(const envoy::config::core::v3::ApiConfigSource::ApiType api_type) {
240
1754
  return (api_type == envoy::config::core::v3::ApiConfigSource::GRPC) ||
241
1754
         (api_type == envoy::config::core::v3::ApiConfigSource::DELTA_GRPC);
242
1754
}
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
1908
    bool xdstp_config_source) {
249
1908
  RETURN_IF_NOT_OK(checkApiConfigSourceNames(
250
1908
      api_config_source,
251
1908
      Runtime::runtimeFeatureEnabled("envoy.restart_features.xds_failover_support") ? 2 : 1));
252

            
253
1895
  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
1806
  } else {
260
1754
    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
1754
  }
266

            
267
1891
  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
1879
  return Envoy::makeOptRef(api_config_source.grpc_services(grpc_service_idx));
273
1891
}
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
1883
    bool skip_cluster_check, int grpc_service_idx, bool xdstp_config_source) {
279

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

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

            
291
absl::Status Utility::translateOpaqueConfig(const Protobuf::Any& typed_config,
292
                                            ProtobufMessage::ValidationVisitor& validation_visitor,
293
128955
                                            Protobuf::Message& out_proto) {
294
128955
  static const std::string struct_type(Protobuf::Struct::default_instance().GetTypeName());
295
128955
  static const std::string typed_struct_type(
296
128955
      xds::type::v3::TypedStruct::default_instance().GetTypeName());
297
128955
  static const std::string legacy_typed_struct_type(
298
128955
      udpa::type::v1::TypedStruct::default_instance().GetTypeName());
299
128955
  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
58351
    absl::string_view type = TypeUtil::typeUrlToDescriptorFullName(typed_config.type_url());
303

            
304
58351
    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
58324
    } 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
58307
    else if (type != struct_type || out_proto.GetTypeName() == struct_type) {
337
58270
      RETURN_IF_NOT_OK(MessageUtil::unpackTo(typed_config, out_proto));
338
58293
    } 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
58351
  }
348
128953
  return absl::OkStatus();
349
128955
}
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
1801
    absl::optional<const uint32_t> default_max_interval_ms) {
356
  // BackoffStrategy config is specified
357
1801
  if (backoff != absl::nullopt) {
358
50
    uint32_t base_interval_ms = PROTOBUF_GET_MS_REQUIRED(backoff.value(), base_interval);
359
50
    uint32_t max_interval_ms =
360
50
        PROTOBUF_GET_MS_OR_DEFAULT(backoff.value(), max_interval, base_interval_ms * 10);
361

            
362
50
    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
45
    return std::make_unique<JitteredExponentialBackOffStrategy>(base_interval_ms, max_interval_ms,
367
45
                                                                random);
368
50
  }
369

            
370
  // default_base_interval_ms must be greater than zero
371
1751
  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
1748
  if (default_max_interval_ms != absl::nullopt) {
377
1745
    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
1744
    return std::make_unique<JitteredExponentialBackOffStrategy>(
382
1744
        default_base_interval_ms, default_max_interval_ms.value(), random);
383
1745
  }
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
1748
}
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
27509
                                              bool last_filter_in_current_config) {
394
27509
  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
27494
  } 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
27488
  return absl::OkStatus();
410
27509
}
411

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