1
#include "source/server/admin/admin.h"
2

            
3
#include <algorithm>
4
#include <cstdint>
5
#include <fstream>
6
#include <string>
7
#include <utility>
8
#include <vector>
9

            
10
#include "envoy/extensions/http/header_validators/envoy_default/v3/header_validator.pb.h"
11
#include "envoy/http/header_validator_factory.h"
12
#include "envoy/server/hot_restart.h"
13
#include "envoy/server/instance.h"
14
#include "envoy/server/options.h"
15
#include "envoy/upstream/cluster_manager.h"
16
#include "envoy/upstream/outlier_detection.h"
17
#include "envoy/upstream/upstream.h"
18

            
19
#include "source/common/access_log/access_log_impl.h"
20
#include "source/common/buffer/buffer_impl.h"
21
#include "source/common/common/assert.h"
22
#include "source/common/common/empty_string.h"
23
#include "source/common/common/fmt.h"
24
#include "source/common/common/matchers.h"
25
#include "source/common/common/mutex_tracer_impl.h"
26
#include "source/common/common/utility.h"
27
#include "source/common/formatter/substitution_formatter.h"
28
#include "source/common/http/codes.h"
29
#include "source/common/http/conn_manager_utility.h"
30
#include "source/common/http/header_map_impl.h"
31
#include "source/common/http/headers.h"
32
#include "source/common/listener_manager/listener_impl.h"
33
#include "source/common/memory/utils.h"
34
#include "source/common/network/listen_socket_impl.h"
35
#include "source/common/protobuf/protobuf.h"
36
#include "source/common/protobuf/utility.h"
37
#include "source/common/router/config_impl.h"
38
#include "source/extensions/request_id/uuid/config.h"
39
#include "source/server/admin/utils.h"
40

            
41
#include "absl/strings/str_join.h"
42
#include "absl/strings/str_replace.h"
43
#include "absl/strings/string_view.h"
44
#include "spdlog/spdlog.h"
45

            
46
namespace Envoy {
47
namespace Server {
48

            
49
62208
ConfigTracker& AdminImpl::getConfigTracker() { return config_tracker_; }
50

            
51
AdminImpl::NullRouteConfigProvider::NullRouteConfigProvider(TimeSource& time_source)
52
10736
    : config_(new Router::NullConfigImpl()), time_source_(time_source) {}
53

            
54
void AdminImpl::startHttpListener(AccessLog::InstanceSharedPtrVector access_logs,
55
                                  Network::Address::InstanceConstSharedPtr address,
56
10696
                                  Network::Socket::OptionsSharedPtr socket_options) {
57
10696
  access_logs_ = std::move(access_logs);
58

            
59
10696
  socket_ = std::make_shared<Network::TcpListenSocket>(address, socket_options, true);
60
10696
  RELEASE_ASSERT(0 == socket_->ioHandle().listen(ENVOY_TCP_BACKLOG_SIZE).return_value_,
61
10696
                 "listen() failed on admin listener");
62
10696
  socket_factories_.emplace_back(std::make_unique<AdminListenSocketFactory>(socket_));
63
10696
  listener_ = std::make_unique<AdminListener>(*this, factory_context_.listenerScope());
64

            
65
10696
  ENVOY_LOG(info, "admin address: {}",
66
10696
            socket().connectionInfoProvider().localAddress()->asString());
67

            
68
10696
  if (!server_.options().adminAddressPath().empty()) {
69
63
    std::ofstream address_out_file(server_.options().adminAddressPath());
70
63
    if (!address_out_file) {
71
1
      ENVOY_LOG(critical, "cannot open admin address output file {} for writing.",
72
1
                server_.options().adminAddressPath());
73
62
    } else {
74
62
      address_out_file << socket_->connectionInfoProvider().localAddress()->asString();
75
62
    }
76
63
  }
77
10696
}
78

            
79
namespace {
80
// Prepends an element to an array, modifying it as passed in.
81
std::vector<absl::string_view> prepend(const absl::string_view first,
82
10735
                                       std::vector<absl::string_view> strings) {
83
10735
  strings.insert(strings.begin(), first);
84
10735
  return strings;
85
10735
}
86

            
87
Http::HeaderValidatorFactoryPtr createHeaderValidatorFactory(
88
10735
    [[maybe_unused]] Server::Configuration::ServerFactoryContext& context) {
89
10735
  Http::HeaderValidatorFactoryPtr header_validator_factory;
90
#ifdef ENVOY_ENABLE_UHV
91
  // Default UHV config matches the admin HTTP validation and normalization config
92
  ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig uhv_config;
93

            
94
  ::envoy::config::core::v3::TypedExtensionConfig config;
95
  config.set_name("default_universal_header_validator_for_admin");
96
  config.mutable_typed_config()->PackFrom(uhv_config);
97

            
98
  auto* factory = Envoy::Config::Utility::getFactory<Http::HeaderValidatorFactoryConfig>(config);
99
  ENVOY_BUG(factory != nullptr, "Default UHV is not linked into binary.");
100

            
101
  header_validator_factory = factory->createFromProto(config.typed_config(), context);
102
  ENVOY_BUG(header_validator_factory != nullptr, "Unable to create default UHV.");
103
#endif
104
10735
  return header_validator_factory;
105
10735
}
106

            
107
} // namespace
108

            
109
AdminImpl::AdminImpl(const std::string& profile_path, Server::Instance& server,
110
                     bool ignore_global_conn_limit)
111
10735
    : server_(server), listener_info_(std::make_shared<ListenerInfoImpl>()),
112
10735
      factory_context_(server, listener_info_),
113
10735
      request_id_extension_(Extensions::RequestId::UUIDRequestIDExtension::defaultInstance(
114
10735
          server_.api().randomGenerator())),
115
10735
      profile_path_(profile_path), stats_(Http::ConnectionManagerImpl::generateStats(
116
10735
                                       "http.admin.", *server_.stats().rootScope())),
117
10735
      null_overload_manager_(server.threadLocal(), false),
118
10735
      tracing_stats_(Http::ConnectionManagerImpl::generateTracingStats("http.admin.",
119
10735
                                                                       *no_op_store_.rootScope())),
120
10735
      route_config_provider_(server.timeSource()),
121
10735
      scoped_route_config_provider_(server.timeSource()), clusters_handler_(server),
122
10735
      config_dump_handler_(config_tracker_, server), init_dump_handler_(server),
123
10735
      stats_handler_(server), logs_handler_(server), profiling_handler_(profile_path),
124
10735
      runtime_handler_(server), listeners_handler_(server), server_cmd_handler_(server),
125
10735
      server_info_handler_(server),
126
      // TODO(jsedgwick) add /runtime_reset endpoint that removes all admin-set values
127
10735
      handlers_{
128
10735
          makeHandler("/", "Admin home page", MAKE_ADMIN_HANDLER(handlerAdminHome), false, false),
129
10735
          makeHandler("/certs", "print certs on machine",
130
10735
                      MAKE_ADMIN_HANDLER(server_info_handler_.handlerCerts), false, false),
131
10735
          makeHandler("/clusters", "upstream cluster status",
132
10735
                      MAKE_ADMIN_HANDLER(clusters_handler_.handlerClusters), false, false,
133
10735
                      {{Admin::ParamDescriptor::Type::String, "filter",
134
10735
                        "Regular expression (Google re2) for filtering clusters by name"}}),
135
10735
          makeHandler(
136
10735
              "/config_dump", "dump current Envoy configs",
137
10735
              MAKE_ADMIN_HANDLER(config_dump_handler_.handlerConfigDump), false, false,
138
10735
              {{Admin::ParamDescriptor::Type::String, "resource", "The resource to dump"},
139
10735
               {Admin::ParamDescriptor::Type::String, "mask",
140
10735
                "The mask to apply. When both resource and mask are specified, "
141
10735
                "the mask is applied to every element in the desired repeated field so that only a "
142
10735
                "subset of fields are returned. The mask is parsed as a Protobuf::FieldMask"},
143
10735
               {Admin::ParamDescriptor::Type::String, "name_regex",
144
10735
                "Dump only the currently loaded configurations whose names match the specified "
145
10735
                "regex. Can be used with both resource and mask query parameters."},
146
10735
               {Admin::ParamDescriptor::Type::Boolean, "include_eds",
147
10735
                "Dump currently loaded configuration including EDS. See the response definition "
148
10735
                "for more information"}}),
149
10735
          makeHandler("/init_dump", "dump current Envoy init manager information (experimental)",
150
10735
                      MAKE_ADMIN_HANDLER(init_dump_handler_.handlerInitDump), false, false,
151
10735
                      {{Admin::ParamDescriptor::Type::String, "mask",
152
10735
                        "The desired component to dump unready targets. The mask is parsed as "
153
10735
                        "a Protobuf::FieldMask. For example, get the unready targets of "
154
10735
                        "all listeners with /init_dump?mask=listener`"}}),
155
10735
          makeHandler("/contention", "dump current Envoy mutex contention stats (if enabled)",
156
10735
                      MAKE_ADMIN_HANDLER(stats_handler_.handlerContention), false, false),
157
10735
          makeHandler("/cpuprofiler", "enable/disable the CPU profiler",
158
10735
                      MAKE_ADMIN_HANDLER(profiling_handler_.handlerCpuProfiler), false, true,
159
10735
                      {{Admin::ParamDescriptor::Type::Enum,
160
10735
                        "enable",
161
10735
                        "enables the CPU profiler",
162
10735
                        {"y", "n"}}}),
163
10735
          makeHandler("/heapprofiler", "enable/disable the heap profiler",
164
10735
                      MAKE_ADMIN_HANDLER(profiling_handler_.handlerHeapProfiler), false, true,
165
10735
                      {{Admin::ParamDescriptor::Type::Enum,
166
10735
                        "enable",
167
10735
                        "enable/disable the heap profiler",
168
10735
                        {"y", "n"}}}),
169
10735
          makeHandler("/heap_dump", "dump current Envoy heap (if supported)",
170
10735
                      MAKE_ADMIN_HANDLER(tcmalloc_profiling_handler_.handlerHeapDump), false,
171
10735
                      false),
172
10735
          makeHandler("/allocprofiler", "enable/disable the allocation profiler (if supported)",
173
10735
                      MAKE_ADMIN_HANDLER(tcmalloc_profiling_handler_.handlerAllocationProfiler),
174
10735
                      false, true,
175
10735
                      {{Admin::ParamDescriptor::Type::Enum,
176
10735
                        "enable",
177
10735
                        "enable/disable the allocation profiler",
178
10735
                        {"y", "n"}}}),
179
10735
          makeHandler("/healthcheck/fail", "cause the server to fail health checks",
180
10735
                      MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerHealthcheckFail), false, true),
181
10735
          makeHandler("/healthcheck/ok", "cause the server to pass health checks",
182
10735
                      MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerHealthcheckOk), false, true),
183
10735
          makeHandler("/help", "print out list of admin commands", MAKE_ADMIN_HANDLER(handlerHelp),
184
10735
                      false, false),
185
10735
          makeHandler("/hot_restart_version", "print the hot restart compatibility version",
186
10735
                      MAKE_ADMIN_HANDLER(server_info_handler_.handlerHotRestartVersion), false,
187
10735
                      false),
188

            
189
          // The logging "level" parameter, if specified as a non-blank entry,
190
          // changes all the logging-paths to that level. So the enum parameter
191
          // needs to include a an empty string as the default (first) option.
192
          // Thus we prepend an empty string to the logging-levels list.
193
10735
          makeHandler("/logging", "query/change logging levels",
194
10735
                      MAKE_ADMIN_HANDLER(logs_handler_.handlerLogging), false, true,
195
10735
                      {{Admin::ParamDescriptor::Type::String, "paths",
196
10735
                        "Change multiple logging levels by setting to "
197
10735
                        "<logger_name1>:<desired_level1>,<logger_name2>:<desired_level2>. "
198
10735
                        "If fine grain logging is enabled, use __FILE__ or a glob experision as "
199
10735
                        "the logger name. "
200
10735
                        "For example, source/common*:warning"},
201
10735
                       {Admin::ParamDescriptor::Type::Enum, "level",
202
10735
                        "desired logging level, this will change all loggers's level",
203
10735
                        prepend("", LogsHandler::levelStrings())}}),
204
10735
          makeHandler("/memory", "print current allocation/heap usage",
205
10735
                      MAKE_ADMIN_HANDLER(server_info_handler_.handlerMemory), false, false),
206
10735
          makeHandler("/memory/tcmalloc", "print TCMalloc stats",
207
10735
                      MAKE_ADMIN_HANDLER(server_info_handler_.handleMemoryTcmallocStats), false,
208
10735
                      false),
209
10735
          makeHandler("/quitquitquit", "exit the server",
210
10735
                      MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerQuitQuitQuit), false, true),
211
10735
          makeHandler("/reset_counters", "reset all counters to zero",
212
10735
                      MAKE_ADMIN_HANDLER(stats_handler_.handlerResetCounters), false, true),
213
10735
          makeHandler(
214
10735
              "/drain_listeners", "drain listeners",
215
10735
              MAKE_ADMIN_HANDLER(listeners_handler_.handlerDrainListeners), false, true,
216
10735
              {{ParamDescriptor::Type::Boolean, "graceful",
217
10735
                "When draining listeners, enter a graceful drain period prior to closing "
218
10735
                "listeners. This behaviour and duration is configurable via server options "
219
10735
                "or CLI"},
220
10735
               {ParamDescriptor::Type::Boolean, "skip_exit",
221
10735
                "When draining listeners, do not exit after the drain period. "
222
10735
                "This must be used with graceful"},
223
10735
               {ParamDescriptor::Type::Boolean, "inboundonly",
224
10735
                "Drains all inbound listeners. traffic_direction field in "
225
10735
                "envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a "
226
10735
                "listener is inbound or outbound."}}),
227
10735
          makeHandler("/server_info", "print server version/status information",
228
10735
                      MAKE_ADMIN_HANDLER(server_info_handler_.handlerServerInfo), false, false),
229
10735
          makeHandler("/ready", "print server state, return 200 if LIVE, otherwise return 503",
230
10735
                      MAKE_ADMIN_HANDLER(server_info_handler_.handlerReady), false, false),
231
10735
          stats_handler_.statsHandler(false /* not active mode */),
232
10735
          makeHandler("/stats/prometheus", "print server stats in prometheus format",
233
10735
                      MAKE_ADMIN_HANDLER(stats_handler_.handlerPrometheusStats), false, false,
234
10735
                      {{ParamDescriptor::Type::Boolean, "usedonly",
235
10735
                        "Only include stats that have been written by system since restart"},
236
10735
                       {ParamDescriptor::Type::Boolean, "text_readouts",
237
10735
                        "Render text_readouts as new gaugues with value 0 (increases Prometheus "
238
10735
                        "data size)"},
239
10735
                       {ParamDescriptor::Type::String, "filter",
240
10735
                        "Regular expression (Google re2) for filtering stats"},
241
10735
                       {ParamDescriptor::Type::Enum,
242
10735
                        "histogram_buckets",
243
10735
                        "Histogram bucket display mode",
244
10735
                        {"cumulative", "summary"}}}),
245
10735
          makeHandler("/stats/recentlookups", "Show recent stat-name lookups",
246
10735
                      MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookups), false, false),
247
10735
          makeHandler("/stats/recentlookups/clear", "clear list of stat-name lookups and counter",
248
10735
                      MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookupsClear), false,
249
10735
                      true),
250
10735
          makeHandler(
251
10735
              "/stats/recentlookups/disable", "disable recording of reset stat-name lookup names",
252
10735
              MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookupsDisable), false, true),
253
10735
          makeHandler(
254
10735
              "/stats/recentlookups/enable", "enable recording of reset stat-name lookup names",
255
10735
              MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookupsEnable), false, true),
256
10735
          makeHandler("/listeners", "print listener info",
257
10735
                      MAKE_ADMIN_HANDLER(listeners_handler_.handlerListenerInfo), false, false,
258
10735
                      {{Admin::ParamDescriptor::Type::Enum,
259
10735
                        "format",
260
10735
                        "File format to use",
261
10735
                        {"text", "json"}}}),
262
10735
          makeHandler("/runtime", "print runtime values",
263
10735
                      MAKE_ADMIN_HANDLER(runtime_handler_.handlerRuntime), false, false),
264
10735
          makeHandler("/runtime_modify",
265
10735
                      "Adds or modifies runtime values as passed in query parameters. To delete a "
266
10735
                      "previously added key, use an empty string as the value. Note that deletion "
267
10735
                      "only applies to overrides added via this endpoint; values loaded from disk "
268
10735
                      "can be modified via override but not deleted. E.g. "
269
10735
                      "?key1=value1&key2=value2...",
270
10735
                      MAKE_ADMIN_HANDLER(runtime_handler_.handlerRuntimeModify), false, true),
271
10735
          makeHandler("/reopen_logs", "reopen access logs",
272
10735
                      MAKE_ADMIN_HANDLER(logs_handler_.handlerReopenLogs), false, true),
273
10735
      },
274
10735
      date_provider_(server.dispatcher().timeSource()),
275
10735
      admin_filter_chain_(std::make_shared<AdminFilterChain>()),
276
10735
      local_reply_(LocalReply::Factory::createDefault()),
277
10735
      ignore_global_conn_limit_(ignore_global_conn_limit),
278
10735
      header_validator_factory_(createHeaderValidatorFactory(server.serverFactoryContext())) {
279
#ifndef NDEBUG
280
  // Verify that no duplicate handlers exist.
281
  absl::flat_hash_set<absl::string_view> handlers;
282
  for (const UrlHandler& handler : handlers_) {
283
    ASSERT(handlers.insert(handler.prefix_).second);
284
  }
285
#endif
286
10735
}
287

            
288
Http::ServerConnectionPtr AdminImpl::createCodec(Network::Connection& connection,
289
                                                 const Buffer::Instance& data,
290
                                                 Http::ServerConnectionCallbacks& callbacks,
291
439
                                                 Server::OverloadManager& overload_manager) {
292
439
  return Http::ConnectionManagerUtility::autoCreateCodec(
293
439
      connection, data, callbacks, *server_.stats().rootScope(), server_.api().randomGenerator(),
294
439
      http1_codec_stats_, http2_codec_stats_, Http::Http1Settings(),
295
439
      ::Envoy::Http2::Utility::initializeAndValidateOptions(
296
439
          envoy::config::core::v3::Http2ProtocolOptions())
297
439
          .value(),
298
439
      maxRequestHeadersKb(), maxRequestHeadersCount(), headersWithUnderscoresAction(),
299
439
      overload_manager);
300
439
}
301

            
302
bool AdminImpl::createNetworkFilterChain(Network::Connection& connection,
303
440
                                         const Filter::NetworkFilterFactoriesList&) {
304
  // Pass in the null overload manager so that the admin interface is accessible even when Envoy
305
  // is overloaded.
306
440
  connection.addReadFilter(Network::ReadFilterSharedPtr{new Http::ConnectionManagerImpl(
307
440
      shared_from_this(), server_.drainManager(), server_.api().randomGenerator(),
308
440
      server_.httpContext(), server_.runtime(), server_.localInfo(), server_.clusterManager(),
309
440
      server_.nullOverloadManager(), server_.timeSource(),
310
440
      envoy::config::core::v3::TrafficDirection::UNSPECIFIED)});
311
440
  return true;
312
440
}
313

            
314
439
bool AdminImpl::createFilterChain(Http::FilterChainFactoryCallbacks& callbacks) const {
315
439
  callbacks.setFilterConfigName("");
316
439
  callbacks.addStreamFilter(std::make_shared<AdminFilter>(*this));
317
439
  return true;
318
439
}
319

            
320
195
void AdminImpl::addAllowlistedPath(Matchers::StringMatcherPtr matcher) {
321
195
  allowlisted_paths_.emplace_back(std::move(matcher));
322
195
}
323

            
324
439
const Matcher::MatchTreePtr<Http::HttpMatchingData>& AdminImpl::forwardClientCertMatcher() const {
325
439
  return forward_client_cert_matcher_;
326
439
}
327

            
328
namespace {
329
// Implements a chunked request for static text.
330
class StaticTextRequest : public Admin::Request {
331
public:
332
5
  StaticTextRequest(absl::string_view response_text, Http::Code code) : code_(code) {
333
5
    response_text_.add(response_text);
334
5
  }
335
18
  StaticTextRequest(Buffer::Instance& response_text, Http::Code code) : code_(code) {
336
18
    response_text_.move(response_text);
337
18
  }
338

            
339
23
  Http::Code start(Http::ResponseHeaderMap&) override { return code_; }
340
23
  bool nextChunk(Buffer::Instance& response) override {
341
23
    response.move(response_text_);
342
23
    return false;
343
23
  }
344

            
345
private:
346
  Buffer::OwnedImpl response_text_;
347
  const Http::Code code_;
348
};
349

            
350
// Implements a streaming Request based on a non-streaming callback that
351
// generates the entire admin output in one shot.
352
class RequestGasket : public Admin::Request {
353
public:
354
  RequestGasket(Admin::HandlerCb handler_cb, AdminStream& admin_stream)
355
510
      : handler_cb_(handler_cb), admin_stream_(admin_stream) {}
356

            
357
332813
  static Admin::GenRequestFn makeGen(Admin::HandlerCb callback) {
358
332813
    return [callback](AdminStream& admin_stream) -> Server::Admin::RequestPtr {
359
510
      return std::make_unique<RequestGasket>(callback, admin_stream);
360
510
    };
361
332813
  }
362

            
363
510
  Http::Code start(Http::ResponseHeaderMap& response_headers) override {
364
510
    return handler_cb_(response_headers, response_, admin_stream_);
365
510
  }
366

            
367
508
  bool nextChunk(Buffer::Instance& response) override {
368
508
    response.move(response_);
369
508
    return false;
370
508
  }
371

            
372
private:
373
  Admin::HandlerCb handler_cb_;
374
  AdminStream& admin_stream_;
375
  Buffer::OwnedImpl response_;
376
};
377

            
378
} // namespace
379

            
380
5
Admin::RequestPtr Admin::makeStaticTextRequest(absl::string_view response, Http::Code code) {
381
5
  return std::make_unique<StaticTextRequest>(response, code);
382
5
}
383

            
384
18
Admin::RequestPtr Admin::makeStaticTextRequest(Buffer::Instance& response, Http::Code code) {
385
18
  return std::make_unique<StaticTextRequest>(response, code);
386
18
}
387

            
388
Http::Code AdminImpl::runCallback(Http::ResponseHeaderMap& response_headers,
389
135
                                  Buffer::Instance& response, AdminStream& admin_stream) {
390
135
  RequestPtr request = makeRequest(admin_stream);
391
135
  Http::Code code = request->start(response_headers);
392
135
  bool more_data;
393
142
  do {
394
142
    more_data = request->nextChunk(response);
395
142
  } while (more_data);
396
135
  Memory::Utils::tryShrinkHeap();
397
135
  return code;
398
135
}
399

            
400
588
Admin::RequestPtr AdminImpl::makeRequest(AdminStream& admin_stream) const {
401
588
  absl::string_view path_and_query = admin_stream.getRequestHeaders().getPathValue();
402
588
  std::string::size_type query_index = path_and_query.find('?');
403
588
  if (query_index == std::string::npos) {
404
285
    query_index = path_and_query.size();
405
285
  }
406
588
  if (!allowlisted_paths_.empty() && !acceptTargetPath(path_and_query)) {
407
2
    ENVOY_LOG(info, "Request to admin interface path {} is not allowed", path_and_query);
408
2
    Buffer::OwnedImpl error_response;
409
2
    error_response.add(fmt::format("request to path {} not allowed", path_and_query));
410
2
    return Admin::makeStaticTextRequest(error_response, Http::Code::Forbidden);
411
2
  }
412

            
413
11467
  for (const UrlHandler& handler : handlers_) {
414
11467
    if (path_and_query.compare(0, query_index, handler.prefix_) == 0) {
415
579
      if (handler.mutates_server_state_) {
416
222
        const absl::string_view method = admin_stream.getRequestHeaders().getMethodValue();
417
222
        if (method != Http::Headers::get().MethodValues.Post) {
418
1
          ENVOY_LOG(error, "admin path \"{}\" mutates state, method={} rather than POST",
419
1
                    handler.prefix_, method);
420
1
          return Admin::makeStaticTextRequest(
421
1
              fmt::format("Method {} not allowed, POST required.", method),
422
1
              Http::Code::MethodNotAllowed);
423
1
        }
424
222
      }
425

            
426
578
      ASSERT(admin_stream.getRequestHeaders().getPathValue() == path_and_query);
427
578
      return handler.handler_(admin_stream);
428
579
    }
429
11467
  }
430

            
431
  // Extra space is emitted below to have "invalid path." be a separate sentence in the
432
  // 404 output from "admin commands are:" in handlerHelp.
433
7
  Buffer::OwnedImpl error_response;
434
7
  error_response.add("invalid path. ");
435
7
  getHelp(error_response);
436
7
  return Admin::makeStaticTextRequest(error_response, Http::Code::NotFound);
437
586
}
438

            
439
16
std::vector<const AdminImpl::UrlHandler*> AdminImpl::sortedHandlers() const {
440
16
  std::vector<const UrlHandler*> sorted_handlers;
441
514
  for (const UrlHandler& handler : handlers_) {
442
514
    sorted_handlers.push_back(&handler);
443
514
  }
444
  // Note: it's generally faster to sort a vector with std::vector than to construct a std::map.
445
16
  std::sort(sorted_handlers.begin(), sorted_handlers.end(),
446
2289
            [](const UrlHandler* h1, const UrlHandler* h2) { return h1->prefix_ < h2->prefix_; });
447
16
  return sorted_handlers;
448
16
}
449

            
450
Http::Code AdminImpl::handlerHelp(Http::ResponseHeaderMap&, Buffer::Instance& response,
451
4
                                  AdminStream&) {
452
4
  getHelp(response);
453
4
  return Http::Code::OK;
454
4
}
455

            
456
11
void AdminImpl::getHelp(Buffer::Instance& response) const {
457
11
  response.add("admin commands are:\n");
458

            
459
  // Prefix order is used during searching, but for printing do them in alpha order.
460
353
  for (const UrlHandler* handler : sortedHandlers()) {
461
353
    const absl::string_view method = handler->mutates_server_state_ ? " (POST)" : "";
462
353
    response.add(fmt::format("  {}{}: {}\n", handler->prefix_, method, handler->help_text_));
463
353
    for (const ParamDescriptor& param : handler->params_) {
464
264
      response.add(fmt::format("      {}: {}", param.id_, param.help_));
465
264
      if (param.type_ == ParamDescriptor::Type::Enum) {
466
99
        response.addFragments({"; One of (", absl::StrJoin(param.enum_choices_, ", "), ")"});
467
99
      }
468
264
      response.add("\n");
469
264
    }
470
353
  }
471
11
}
472

            
473
439
const Network::Address::Instance& AdminImpl::localAddress() {
474
439
  return *server_.localInfo().address();
475
439
}
476

            
477
AdminImpl::UrlHandler AdminImpl::makeHandler(const std::string& prefix,
478
                                             const std::string& help_text, HandlerCb callback,
479
                                             bool removable, bool mutates_state,
480
332785
                                             const ParamDescriptorVec& params) {
481
332785
  return UrlHandler{prefix,    help_text,     RequestGasket::makeGen(callback),
482
332785
                    removable, mutates_state, params};
483
332785
}
484

            
485
bool AdminImpl::addStreamingHandler(const std::string& prefix, const std::string& help_text,
486
                                    GenRequestFn callback, bool removable, bool mutates_state,
487
50
                                    const ParamDescriptorVec& params) {
488
50
  ASSERT(prefix.size() > 1);
489
50
  ASSERT(prefix[0] == '/');
490

            
491
  // Sanitize prefix and help_text to ensure no XSS can be injected, as
492
  // we are injecting these strings into HTML that runs in a domain that
493
  // can mutate Envoy server state. Also rule out some characters that
494
  // make no sense as part of a URL path: ? and :.
495
50
  const std::string::size_type pos = prefix.find_first_of("&\"'<>?:");
496
50
  if (pos != std::string::npos) {
497
2
    ENVOY_LOG(error, "filter \"{}\" contains invalid character '{}'", prefix, prefix[pos]);
498
2
    return false;
499
2
  }
500

            
501
48
  auto it = std::find_if(handlers_.cbegin(), handlers_.cend(),
502
1538
                         [&prefix](const UrlHandler& entry) { return prefix == entry.prefix_; });
503
48
  if (it == handlers_.end()) {
504
46
    handlers_.push_back({prefix, help_text, callback, removable, mutates_state, params});
505
46
    return true;
506
46
  }
507
2
  return false;
508
48
}
509

            
510
bool AdminImpl::addHandler(const std::string& prefix, const std::string& help_text,
511
                           HandlerCb callback, bool removable, bool mutates_state,
512
28
                           const ParamDescriptorVec& params) {
513
28
  return addStreamingHandler(prefix, help_text, RequestGasket::makeGen(callback), removable,
514
28
                             mutates_state, params);
515
28
}
516

            
517
21
bool AdminImpl::removeHandler(const std::string& prefix) {
518
21
  const size_t size_before_removal = handlers_.size();
519
21
  handlers_.remove_if(
520
691
      [&prefix](const UrlHandler& entry) { return prefix == entry.prefix_ && entry.removable_; });
521
21
  if (handlers_.size() != size_before_removal) {
522
17
    return true;
523
17
  }
524
4
  return false;
525
21
}
526

            
527
Http::Code AdminImpl::request(absl::string_view path_and_query, absl::string_view method,
528
46
                              Http::ResponseHeaderMap& response_headers, std::string& body) {
529
46
  AdminFilter filter(*this);
530

            
531
46
  auto request_headers = Http::RequestHeaderMapImpl::create();
532
46
  request_headers->setMethod(method);
533
46
  request_headers->setPath(path_and_query);
534
46
  filter.decodeHeaders(*request_headers, false);
535
46
  Buffer::OwnedImpl response;
536

            
537
46
  Http::Code code = runCallback(response_headers, response, filter);
538
46
  Utility::populateFallbackResponseHeaders(code, response_headers);
539
46
  body = response.toString();
540
46
  return code;
541
46
}
542

            
543
void AdminImpl::closeSocket() {
544
  if (socket_) {
545
    socket_->close();
546
  }
547
}
548

            
549
10634
void AdminImpl::addListenerToHandler(Network::ConnectionHandler* handler) {
550
10634
  if (listener_) {
551
10633
    handler->addListener(absl::nullopt, *listener_, server_.runtime(),
552
10633
                         server_.api().randomGenerator());
553
10633
  }
554
10634
}
555

            
556
#ifdef ENVOY_ENABLE_UHV
557
::Envoy::Http::HeaderValidatorStats&
558
AdminImpl::getHeaderValidatorStats([[maybe_unused]] Http::Protocol protocol) {
559
  switch (protocol) {
560
  case Http::Protocol::Http10:
561
  case Http::Protocol::Http11:
562
    return Http::Http1::CodecStats::atomicGet(http1_codec_stats_, *server_.stats().rootScope());
563
  case Http::Protocol::Http3:
564
    IS_ENVOY_BUG("HTTP/3 is not supported for admin UI");
565
    // Return H/2 stats object, since we do not have H/3 stats.
566
    ABSL_FALLTHROUGH_INTENDED;
567
  case Http::Protocol::Http2:
568
    return Http::Http2::CodecStats::atomicGet(http2_codec_stats_, *server_.stats().rootScope());
569
  }
570
  PANIC_DUE_TO_CORRUPT_ENUM;
571
}
572
#endif
573

            
574
} // namespace Server
575
} // namespace Envoy