Line data Source code
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/mutex_tracer_impl.h"
25 : #include "source/common/common/utility.h"
26 : #include "source/common/formatter/substitution_formatter.h"
27 : #include "source/common/http/codes.h"
28 : #include "source/common/http/conn_manager_utility.h"
29 : #include "source/common/http/header_map_impl.h"
30 : #include "source/common/http/headers.h"
31 : #include "source/common/listener_manager/listener_impl.h"
32 : #include "source/common/memory/utils.h"
33 : #include "source/common/network/listen_socket_impl.h"
34 : #include "source/common/protobuf/protobuf.h"
35 : #include "source/common/protobuf/utility.h"
36 : #include "source/common/router/config_impl.h"
37 : #include "source/extensions/request_id/uuid/config.h"
38 : #include "source/server/admin/utils.h"
39 :
40 : #include "absl/strings/str_join.h"
41 : #include "absl/strings/str_replace.h"
42 : #include "absl/strings/string_view.h"
43 : #include "spdlog/spdlog.h"
44 :
45 : namespace Envoy {
46 : namespace Server {
47 :
48 771 : ConfigTracker& AdminImpl::getConfigTracker() { return config_tracker_; }
49 :
50 : AdminImpl::NullRouteConfigProvider::NullRouteConfigProvider(TimeSource& time_source)
51 134 : : config_(new Router::NullConfigImpl()), time_source_(time_source) {}
52 :
53 : void AdminImpl::startHttpListener(std::list<AccessLog::InstanceSharedPtr> access_logs,
54 : Network::Address::InstanceConstSharedPtr address,
55 98 : Network::Socket::OptionsSharedPtr socket_options) {
56 98 : access_logs_ = std::move(access_logs);
57 :
58 98 : null_overload_manager_.start();
59 98 : socket_ = std::make_shared<Network::TcpListenSocket>(address, socket_options, true);
60 98 : RELEASE_ASSERT(0 == socket_->ioHandle().listen(ENVOY_TCP_BACKLOG_SIZE).return_value_,
61 98 : "listen() failed on admin listener");
62 98 : socket_factories_.emplace_back(std::make_unique<AdminListenSocketFactory>(socket_));
63 98 : listener_ = std::make_unique<AdminListener>(*this, factory_context_.listenerScope());
64 :
65 98 : ENVOY_LOG(info, "admin address: {}",
66 98 : socket().connectionInfoProvider().localAddress()->asString());
67 :
68 98 : if (!server_.options().adminAddressPath().empty()) {
69 0 : std::ofstream address_out_file(server_.options().adminAddressPath());
70 0 : if (!address_out_file) {
71 0 : ENVOY_LOG(critical, "cannot open admin address output file {} for writing.",
72 0 : server_.options().adminAddressPath());
73 0 : } else {
74 0 : address_out_file << socket_->connectionInfoProvider().localAddress()->asString();
75 0 : }
76 0 : }
77 98 : }
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 134 : std::vector<absl::string_view> strings) {
83 134 : strings.insert(strings.begin(), first);
84 134 : return strings;
85 134 : }
86 :
87 : Http::HeaderValidatorFactoryPtr createHeaderValidatorFactory(
88 134 : [[maybe_unused]] Server::Configuration::ServerFactoryContext& context) {
89 134 : 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 134 : return header_validator_factory;
105 134 : }
106 :
107 : } // namespace
108 :
109 : AdminImpl::AdminImpl(const std::string& profile_path, Server::Instance& server,
110 : bool ignore_global_conn_limit)
111 : : server_(server), listener_info_(std::make_shared<ListenerInfoImpl>()),
112 : factory_context_(server, listener_info_),
113 : request_id_extension_(Extensions::RequestId::UUIDRequestIDExtension::defaultInstance(
114 : server_.api().randomGenerator())),
115 : profile_path_(profile_path), stats_(Http::ConnectionManagerImpl::generateStats(
116 : "http.admin.", *server_.stats().rootScope())),
117 : null_overload_manager_(server_.threadLocal(), false),
118 : tracing_stats_(Http::ConnectionManagerImpl::generateTracingStats("http.admin.",
119 : *no_op_store_.rootScope())),
120 : route_config_provider_(server.timeSource()),
121 : scoped_route_config_provider_(server.timeSource()), clusters_handler_(server),
122 : config_dump_handler_(config_tracker_, server), init_dump_handler_(server),
123 : stats_handler_(server), logs_handler_(server), profiling_handler_(profile_path),
124 : runtime_handler_(server), listeners_handler_(server), server_cmd_handler_(server),
125 : server_info_handler_(server),
126 : // TODO(jsedgwick) add /runtime_reset endpoint that removes all admin-set values
127 : handlers_{
128 : makeHandler("/", "Admin home page", MAKE_ADMIN_HANDLER(handlerAdminHome), false, false),
129 : makeHandler("/certs", "print certs on machine",
130 : MAKE_ADMIN_HANDLER(server_info_handler_.handlerCerts), false, false),
131 : makeHandler("/clusters", "upstream cluster status",
132 : MAKE_ADMIN_HANDLER(clusters_handler_.handlerClusters), false, false),
133 : makeHandler(
134 : "/config_dump", "dump current Envoy configs (experimental)",
135 : MAKE_ADMIN_HANDLER(config_dump_handler_.handlerConfigDump), false, false,
136 : {{Admin::ParamDescriptor::Type::String, "resource", "The resource to dump"},
137 : {Admin::ParamDescriptor::Type::String, "mask",
138 : "The mask to apply. When both resource and mask are specified, "
139 : "the mask is applied to every element in the desired repeated field so that only a "
140 : "subset of fields are returned. The mask is parsed as a ProtobufWkt::FieldMask"},
141 : {Admin::ParamDescriptor::Type::String, "name_regex",
142 : "Dump only the currently loaded configurations whose names match the specified "
143 : "regex. Can be used with both resource and mask query parameters."},
144 : {Admin::ParamDescriptor::Type::Boolean, "include_eds",
145 : "Dump currently loaded configuration including EDS. See the response definition "
146 : "for more information"}}),
147 : makeHandler("/init_dump", "dump current Envoy init manager information (experimental)",
148 : MAKE_ADMIN_HANDLER(init_dump_handler_.handlerInitDump), false, false,
149 : {{Admin::ParamDescriptor::Type::String, "mask",
150 : "The desired component to dump unready targets. The mask is parsed as "
151 : "a ProtobufWkt::FieldMask. For example, get the unready targets of "
152 : "all listeners with /init_dump?mask=listener`"}}),
153 : makeHandler("/contention", "dump current Envoy mutex contention stats (if enabled)",
154 : MAKE_ADMIN_HANDLER(stats_handler_.handlerContention), false, false),
155 : makeHandler("/cpuprofiler", "enable/disable the CPU profiler",
156 : MAKE_ADMIN_HANDLER(profiling_handler_.handlerCpuProfiler), false, true,
157 : {{Admin::ParamDescriptor::Type::Enum,
158 : "enable",
159 : "enables the CPU profiler",
160 : {"y", "n"}}}),
161 : makeHandler("/heapprofiler", "enable/disable the heap profiler",
162 : MAKE_ADMIN_HANDLER(profiling_handler_.handlerHeapProfiler), false, true,
163 : {{Admin::ParamDescriptor::Type::Enum,
164 : "enable",
165 : "enable/disable the heap profiler",
166 : {"y", "n"}}}),
167 : makeHandler("/heap_dump", "dump current Envoy heap (if supported)",
168 : MAKE_ADMIN_HANDLER(tcmalloc_profiling_handler_.handlerHeapDump), false,
169 : false),
170 : makeHandler("/healthcheck/fail", "cause the server to fail health checks",
171 : MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerHealthcheckFail), false, true),
172 : makeHandler("/healthcheck/ok", "cause the server to pass health checks",
173 : MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerHealthcheckOk), false, true),
174 : makeHandler("/help", "print out list of admin commands", MAKE_ADMIN_HANDLER(handlerHelp),
175 : false, false),
176 : makeHandler("/hot_restart_version", "print the hot restart compatibility version",
177 : MAKE_ADMIN_HANDLER(server_info_handler_.handlerHotRestartVersion), false,
178 : false),
179 :
180 : // The logging "level" parameter, if specified as a non-blank entry,
181 : // changes all the logging-paths to that level. So the enum parameter
182 : // needs to include a an empty string as the default (first) option.
183 : // Thus we prepend an empty string to the logging-levels list.
184 : makeHandler("/logging", "query/change logging levels",
185 : MAKE_ADMIN_HANDLER(logs_handler_.handlerLogging), false, true,
186 : {{Admin::ParamDescriptor::Type::String, "paths",
187 : "Change multiple logging levels by setting to "
188 : "<logger_name1>:<desired_level1>,<logger_name2>:<desired_level2>."},
189 : {Admin::ParamDescriptor::Type::Enum, "level", "desired logging level",
190 : prepend("", LogsHandler::levelStrings())}}),
191 : makeHandler("/memory", "print current allocation/heap usage",
192 : MAKE_ADMIN_HANDLER(server_info_handler_.handlerMemory), false, false),
193 : makeHandler("/quitquitquit", "exit the server",
194 : MAKE_ADMIN_HANDLER(server_cmd_handler_.handlerQuitQuitQuit), false, true),
195 : makeHandler("/reset_counters", "reset all counters to zero",
196 : MAKE_ADMIN_HANDLER(stats_handler_.handlerResetCounters), false, true),
197 : makeHandler(
198 : "/drain_listeners", "drain listeners",
199 : MAKE_ADMIN_HANDLER(listeners_handler_.handlerDrainListeners), false, true,
200 : {{ParamDescriptor::Type::Boolean, "graceful",
201 : "When draining listeners, enter a graceful drain period prior to closing "
202 : "listeners. This behaviour and duration is configurable via server options "
203 : "or CLI"},
204 : {ParamDescriptor::Type::Boolean, "skip_exit",
205 : "When draining listeners, do not exit after the drain period. "
206 : "This must be used with graceful"},
207 : {ParamDescriptor::Type::Boolean, "inboundonly",
208 : "Drains all inbound listeners. traffic_direction field in "
209 : "envoy_v3_api_msg_config.listener.v3.Listener is used to determine whether a "
210 : "listener is inbound or outbound."}}),
211 : makeHandler("/server_info", "print server version/status information",
212 : MAKE_ADMIN_HANDLER(server_info_handler_.handlerServerInfo), false, false),
213 : makeHandler("/ready", "print server state, return 200 if LIVE, otherwise return 503",
214 : MAKE_ADMIN_HANDLER(server_info_handler_.handlerReady), false, false),
215 : stats_handler_.statsHandler(false /* not active mode */),
216 : makeHandler("/stats/prometheus", "print server stats in prometheus format",
217 : MAKE_ADMIN_HANDLER(stats_handler_.handlerPrometheusStats), false, false,
218 : {{ParamDescriptor::Type::Boolean, "usedonly",
219 : "Only include stats that have been written by system since restart"},
220 : {ParamDescriptor::Type::Boolean, "text_readouts",
221 : "Render text_readouts as new gaugues with value 0 (increases Prometheus "
222 : "data size)"},
223 : {ParamDescriptor::Type::String, "filter",
224 : "Regular expression (Google re2) for filtering stats"}}),
225 : makeHandler("/stats/recentlookups", "Show recent stat-name lookups",
226 : MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookups), false, false),
227 : makeHandler("/stats/recentlookups/clear", "clear list of stat-name lookups and counter",
228 : MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookupsClear), false,
229 : true),
230 : makeHandler(
231 : "/stats/recentlookups/disable", "disable recording of reset stat-name lookup names",
232 : MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookupsDisable), false, true),
233 : makeHandler(
234 : "/stats/recentlookups/enable", "enable recording of reset stat-name lookup names",
235 : MAKE_ADMIN_HANDLER(stats_handler_.handlerStatsRecentLookupsEnable), false, true),
236 : makeHandler("/listeners", "print listener info",
237 : MAKE_ADMIN_HANDLER(listeners_handler_.handlerListenerInfo), false, false,
238 : {{Admin::ParamDescriptor::Type::Enum,
239 : "format",
240 : "File format to use",
241 : {"text", "json"}}}),
242 : makeHandler("/runtime", "print runtime values",
243 : MAKE_ADMIN_HANDLER(runtime_handler_.handlerRuntime), false, false),
244 : makeHandler("/runtime_modify",
245 : "Adds or modifies runtime values as passed in query parameters. To delete a "
246 : "previously added key, use an empty string as the value. Note that deletion "
247 : "only applies to overrides added via this endpoint; values loaded from disk "
248 : "can be modified via override but not deleted. E.g. "
249 : "?key1=value1&key2=value2...",
250 : MAKE_ADMIN_HANDLER(runtime_handler_.handlerRuntimeModify), false, true),
251 : makeHandler("/reopen_logs", "reopen access logs",
252 : MAKE_ADMIN_HANDLER(logs_handler_.handlerReopenLogs), false, true),
253 : },
254 : date_provider_(server.dispatcher().timeSource()),
255 : admin_filter_chain_(std::make_shared<AdminFilterChain>()),
256 : local_reply_(LocalReply::Factory::createDefault()),
257 : ignore_global_conn_limit_(ignore_global_conn_limit),
258 134 : header_validator_factory_(createHeaderValidatorFactory(server.serverFactoryContext())) {
259 : #ifndef NDEBUG
260 : // Verify that no duplicate handlers exist.
261 : absl::flat_hash_set<absl::string_view> handlers;
262 : for (const UrlHandler& handler : handlers_) {
263 : ASSERT(handlers.insert(handler.prefix_).second);
264 : }
265 : #endif
266 134 : }
267 :
268 : Http::ServerConnectionPtr AdminImpl::createCodec(Network::Connection& connection,
269 : const Buffer::Instance& data,
270 : Http::ServerConnectionCallbacks& callbacks,
271 98 : Server::OverloadManager& overload_manager) {
272 98 : return Http::ConnectionManagerUtility::autoCreateCodec(
273 98 : connection, data, callbacks, *server_.stats().rootScope(), server_.api().randomGenerator(),
274 98 : http1_codec_stats_, http2_codec_stats_, Http::Http1Settings(),
275 98 : ::Envoy::Http2::Utility::initializeAndValidateOptions(
276 98 : envoy::config::core::v3::Http2ProtocolOptions()),
277 98 : maxRequestHeadersKb(), maxRequestHeadersCount(), headersWithUnderscoresAction(),
278 98 : overload_manager);
279 98 : }
280 :
281 : bool AdminImpl::createNetworkFilterChain(Network::Connection& connection,
282 98 : const Filter::NetworkFilterFactoriesList&) {
283 : // Pass in the null overload manager so that the admin interface is accessible even when Envoy
284 : // is overloaded.
285 98 : connection.addReadFilter(Network::ReadFilterSharedPtr{new Http::ConnectionManagerImpl(
286 98 : *this, server_.drainManager(), server_.api().randomGenerator(), server_.httpContext(),
287 98 : server_.runtime(), server_.localInfo(), server_.clusterManager(), null_overload_manager_,
288 98 : server_.timeSource())});
289 98 : return true;
290 98 : }
291 :
292 : bool AdminImpl::createFilterChain(Http::FilterChainManager& manager, bool,
293 98 : const Http::FilterChainOptions&) const {
294 98 : Http::FilterFactoryCb factory = [this](Http::FilterChainFactoryCallbacks& callbacks) {
295 98 : callbacks.addStreamFilter(std::make_shared<AdminFilter>(createRequestFunction()));
296 98 : };
297 98 : manager.applyFilterFactoryCb({}, factory);
298 98 : return true;
299 98 : }
300 :
301 : namespace {
302 : // Implements a chunked request for static text.
303 : class StaticTextRequest : public Admin::Request {
304 : public:
305 0 : StaticTextRequest(absl::string_view response_text, Http::Code code) : code_(code) {
306 0 : response_text_.add(response_text);
307 0 : }
308 0 : StaticTextRequest(Buffer::Instance& response_text, Http::Code code) : code_(code) {
309 0 : response_text_.move(response_text);
310 0 : }
311 :
312 0 : Http::Code start(Http::ResponseHeaderMap&) override { return code_; }
313 0 : bool nextChunk(Buffer::Instance& response) override {
314 0 : response.move(response_text_);
315 0 : return false;
316 0 : }
317 :
318 : private:
319 : Buffer::OwnedImpl response_text_;
320 : const Http::Code code_;
321 : };
322 :
323 : // Implements a streaming Request based on a non-streaming callback that
324 : // generates the entire admin output in one shot.
325 : class RequestGasket : public Admin::Request {
326 : public:
327 : RequestGasket(Admin::HandlerCb handler_cb, AdminStream& admin_stream)
328 98 : : handler_cb_(handler_cb), admin_stream_(admin_stream) {}
329 :
330 3886 : static Admin::GenRequestFn makeGen(Admin::HandlerCb callback) {
331 3886 : return [callback](AdminStream& admin_stream) -> Server::Admin::RequestPtr {
332 98 : return std::make_unique<RequestGasket>(callback, admin_stream);
333 98 : };
334 3886 : }
335 :
336 98 : Http::Code start(Http::ResponseHeaderMap& response_headers) override {
337 98 : return handler_cb_(response_headers, response_, admin_stream_);
338 98 : }
339 :
340 98 : bool nextChunk(Buffer::Instance& response) override {
341 98 : response.move(response_);
342 98 : return false;
343 98 : }
344 :
345 : private:
346 : Admin::HandlerCb handler_cb_;
347 : AdminStream& admin_stream_;
348 : Buffer::OwnedImpl response_;
349 : };
350 :
351 : } // namespace
352 :
353 0 : Admin::RequestPtr Admin::makeStaticTextRequest(absl::string_view response, Http::Code code) {
354 0 : return std::make_unique<StaticTextRequest>(response, code);
355 0 : }
356 :
357 0 : Admin::RequestPtr Admin::makeStaticTextRequest(Buffer::Instance& response, Http::Code code) {
358 0 : return std::make_unique<StaticTextRequest>(response, code);
359 0 : }
360 :
361 : Http::Code AdminImpl::runCallback(Http::ResponseHeaderMap& response_headers,
362 0 : Buffer::Instance& response, AdminStream& admin_stream) {
363 0 : RequestPtr request = makeRequest(admin_stream);
364 0 : Http::Code code = request->start(response_headers);
365 0 : bool more_data;
366 0 : do {
367 0 : more_data = request->nextChunk(response);
368 0 : } while (more_data);
369 0 : Memory::Utils::tryShrinkHeap();
370 0 : return code;
371 0 : }
372 :
373 98 : Admin::RequestPtr AdminImpl::makeRequest(AdminStream& admin_stream) const {
374 98 : absl::string_view path_and_query = admin_stream.getRequestHeaders().getPathValue();
375 98 : std::string::size_type query_index = path_and_query.find('?');
376 98 : if (query_index == std::string::npos) {
377 98 : query_index = path_and_query.size();
378 98 : }
379 :
380 392 : for (const UrlHandler& handler : handlers_) {
381 392 : if (path_and_query.compare(0, query_index, handler.prefix_) == 0) {
382 98 : if (handler.mutates_server_state_) {
383 0 : const absl::string_view method = admin_stream.getRequestHeaders().getMethodValue();
384 0 : if (method != Http::Headers::get().MethodValues.Post) {
385 0 : ENVOY_LOG(error, "admin path \"{}\" mutates state, method={} rather than POST",
386 0 : handler.prefix_, method);
387 0 : return Admin::makeStaticTextRequest(
388 0 : fmt::format("Method {} not allowed, POST required.", method),
389 0 : Http::Code::MethodNotAllowed);
390 0 : }
391 0 : }
392 :
393 98 : ASSERT(admin_stream.getRequestHeaders().getPathValue() == path_and_query);
394 98 : return handler.handler_(admin_stream);
395 98 : }
396 392 : }
397 :
398 : // Extra space is emitted below to have "invalid path." be a separate sentence in the
399 : // 404 output from "admin commands are:" in handlerHelp.
400 0 : Buffer::OwnedImpl error_response;
401 0 : error_response.add("invalid path. ");
402 0 : getHelp(error_response);
403 0 : return Admin::makeStaticTextRequest(error_response, Http::Code::NotFound);
404 98 : }
405 :
406 0 : std::vector<const AdminImpl::UrlHandler*> AdminImpl::sortedHandlers() const {
407 0 : std::vector<const UrlHandler*> sorted_handlers;
408 0 : for (const UrlHandler& handler : handlers_) {
409 0 : sorted_handlers.push_back(&handler);
410 0 : }
411 : // Note: it's generally faster to sort a vector with std::vector than to construct a std::map.
412 0 : std::sort(sorted_handlers.begin(), sorted_handlers.end(),
413 0 : [](const UrlHandler* h1, const UrlHandler* h2) { return h1->prefix_ < h2->prefix_; });
414 0 : return sorted_handlers;
415 0 : }
416 :
417 : Http::Code AdminImpl::handlerHelp(Http::ResponseHeaderMap&, Buffer::Instance& response,
418 0 : AdminStream&) {
419 0 : getHelp(response);
420 0 : return Http::Code::OK;
421 0 : }
422 :
423 0 : void AdminImpl::getHelp(Buffer::Instance& response) const {
424 0 : response.add("admin commands are:\n");
425 :
426 : // Prefix order is used during searching, but for printing do them in alpha order.
427 0 : for (const UrlHandler* handler : sortedHandlers()) {
428 0 : const absl::string_view method = handler->mutates_server_state_ ? " (POST)" : "";
429 0 : response.add(fmt::format(" {}{}: {}\n", handler->prefix_, method, handler->help_text_));
430 0 : for (const ParamDescriptor& param : handler->params_) {
431 0 : response.add(fmt::format(" {}: {}", param.id_, param.help_));
432 0 : if (param.type_ == ParamDescriptor::Type::Enum) {
433 0 : response.addFragments({"; One of (", absl::StrJoin(param.enum_choices_, ", "), ")"});
434 0 : }
435 0 : response.add("\n");
436 0 : }
437 0 : }
438 0 : }
439 :
440 98 : const Network::Address::Instance& AdminImpl::localAddress() {
441 98 : return *server_.localInfo().address();
442 98 : }
443 :
444 : AdminImpl::UrlHandler AdminImpl::makeHandler(const std::string& prefix,
445 : const std::string& help_text, HandlerCb callback,
446 : bool removable, bool mutates_state,
447 3886 : const ParamDescriptorVec& params) {
448 3886 : return UrlHandler{prefix, help_text, RequestGasket::makeGen(callback),
449 3886 : removable, mutates_state, params};
450 3886 : }
451 :
452 : bool AdminImpl::addStreamingHandler(const std::string& prefix, const std::string& help_text,
453 : GenRequestFn callback, bool removable, bool mutates_state,
454 0 : const ParamDescriptorVec& params) {
455 0 : ASSERT(prefix.size() > 1);
456 0 : ASSERT(prefix[0] == '/');
457 :
458 : // Sanitize prefix and help_text to ensure no XSS can be injected, as
459 : // we are injecting these strings into HTML that runs in a domain that
460 : // can mutate Envoy server state. Also rule out some characters that
461 : // make no sense as part of a URL path: ? and :.
462 0 : const std::string::size_type pos = prefix.find_first_of("&\"'<>?:");
463 0 : if (pos != std::string::npos) {
464 0 : ENVOY_LOG(error, "filter \"{}\" contains invalid character '{}'", prefix, prefix[pos]);
465 0 : return false;
466 0 : }
467 :
468 0 : auto it = std::find_if(handlers_.cbegin(), handlers_.cend(),
469 0 : [&prefix](const UrlHandler& entry) { return prefix == entry.prefix_; });
470 0 : if (it == handlers_.end()) {
471 0 : handlers_.push_back({prefix, help_text, callback, removable, mutates_state, params});
472 0 : return true;
473 0 : }
474 0 : return false;
475 0 : }
476 :
477 : bool AdminImpl::addHandler(const std::string& prefix, const std::string& help_text,
478 : HandlerCb callback, bool removable, bool mutates_state,
479 0 : const ParamDescriptorVec& params) {
480 0 : return addStreamingHandler(prefix, help_text, RequestGasket::makeGen(callback), removable,
481 0 : mutates_state, params);
482 0 : }
483 :
484 0 : bool AdminImpl::removeHandler(const std::string& prefix) {
485 0 : const size_t size_before_removal = handlers_.size();
486 0 : handlers_.remove_if(
487 0 : [&prefix](const UrlHandler& entry) { return prefix == entry.prefix_ && entry.removable_; });
488 0 : if (handlers_.size() != size_before_removal) {
489 0 : return true;
490 0 : }
491 0 : return false;
492 0 : }
493 :
494 : Http::Code AdminImpl::request(absl::string_view path_and_query, absl::string_view method,
495 0 : Http::ResponseHeaderMap& response_headers, std::string& body) {
496 0 : AdminFilter filter(createRequestFunction());
497 :
498 0 : auto request_headers = Http::RequestHeaderMapImpl::create();
499 0 : request_headers->setMethod(method);
500 0 : request_headers->setPath(path_and_query);
501 0 : filter.decodeHeaders(*request_headers, false);
502 0 : Buffer::OwnedImpl response;
503 :
504 0 : Http::Code code = runCallback(response_headers, response, filter);
505 0 : Utility::populateFallbackResponseHeaders(code, response_headers);
506 0 : body = response.toString();
507 0 : return code;
508 0 : }
509 :
510 0 : void AdminImpl::closeSocket() {
511 0 : if (socket_) {
512 0 : socket_->close();
513 0 : }
514 0 : }
515 :
516 98 : void AdminImpl::addListenerToHandler(Network::ConnectionHandler* handler) {
517 98 : if (listener_) {
518 98 : handler->addListener(absl::nullopt, *listener_, server_.runtime(),
519 98 : server_.api().randomGenerator());
520 98 : }
521 98 : }
522 :
523 : #ifdef ENVOY_ENABLE_UHV
524 : ::Envoy::Http::HeaderValidatorStats&
525 : AdminImpl::getHeaderValidatorStats([[maybe_unused]] Http::Protocol protocol) {
526 : switch (protocol) {
527 : case Http::Protocol::Http10:
528 : case Http::Protocol::Http11:
529 : return Http::Http1::CodecStats::atomicGet(http1_codec_stats_, *server_.stats().rootScope());
530 : case Http::Protocol::Http3:
531 : IS_ENVOY_BUG("HTTP/3 is not supported for admin UI");
532 : // Return H/2 stats object, since we do not have H/3 stats.
533 : ABSL_FALLTHROUGH_INTENDED;
534 : case Http::Protocol::Http2:
535 : return Http::Http2::CodecStats::atomicGet(http2_codec_stats_, *server_.stats().rootScope());
536 : }
537 : PANIC_DUE_TO_CORRUPT_ENUM;
538 : }
539 : #endif
540 :
541 : } // namespace Server
542 : } // namespace Envoy
|