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