From fdbdc4cb31d45c68e935b06d082fc390f291b807 Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Fri, 4 Jun 2021 22:36:58 -0400 Subject: [PATCH 1/2] [flagutil] Add flag type for string sets Signed-off-by: Andrew Mason --- go/flagutil/sets.go | 80 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 go/flagutil/sets.go diff --git a/go/flagutil/sets.go b/go/flagutil/sets.go new file mode 100644 index 00000000000..cfe21481f42 --- /dev/null +++ b/go/flagutil/sets.go @@ -0,0 +1,80 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package flagutil + +import ( + "flag" + "strings" + + "github.com/spf13/pflag" + "k8s.io/apimachinery/pkg/util/sets" +) + +var ( + _ flag.Value = (*StringSetFlag)(nil) + _ pflag.Value = (*StringSetFlag)(nil) +) + +// StringSetFlag can be used to collect multiple instances of a flag into a set +// of values. +// +// For example, defining the following: +// +// var x flagutil.StringSetFlag +// flag.Var(&x, "foo", "") +// +// And then specifying "-foo x -foo y -foo x", will result in a set of {x, y}. +// +// In addition to implemnting the standard flag.Value interface, it also +// provides an implementation of pflag.Value, so it is usable in libraries like +// cobra. +type StringSetFlag struct { + set sets.String +} + +// ToSet returns the underlying string set, or an empty set if the underlying +// set is nil. +func (set *StringSetFlag) ToSet() sets.String { + if set.set == nil { + set.set = sets.NewString() + } + + return set.set +} + +// Set is part of the pflag.Value and flag.Value interfaces. +func (set *StringSetFlag) Set(s string) error { + if set.set == nil { + set.set = sets.NewString(s) + return nil + } + + set.set.Insert(s) + return nil +} + +// String is part of the pflag.Value and flag.Value interfaces. +func (set *StringSetFlag) String() string { + if set.set == nil { + return "" + } + + return strings.Join(set.set.List(), ", ") +} + +// Type is part of the pflag.Value interface. +func (set *StringSetFlag) Type() string { return "StringSetFlag" } From 100a9db02e68837e47897f4201d4f0d3f3ec590c Mon Sep 17 00:00:00 2001 From: Andrew Mason Date: Sat, 5 Jun 2021 09:47:59 -0400 Subject: [PATCH 2/2] Add optional debugging endpoints to vtadmin's http server Signed-off-by: Andrew Mason --- go/cmd/vtadmin/main.go | 4 ++ go/vt/vtadmin/api.go | 15 +++++ go/vt/vtadmin/http/api.go | 5 +- go/vt/vtadmin/http/debug/debug.go | 102 ++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 go/vt/vtadmin/http/debug/debug.go diff --git a/go/cmd/vtadmin/main.go b/go/cmd/vtadmin/main.go index 3ff8792377d..464bf6104a5 100644 --- a/go/cmd/vtadmin/main.go +++ b/go/cmd/vtadmin/main.go @@ -31,6 +31,7 @@ import ( "vitess.io/vitess/go/vt/vtadmin/cluster" "vitess.io/vitess/go/vt/vtadmin/grpcserver" vtadminhttp "vitess.io/vitess/go/vt/vtadmin/http" + "vitess.io/vitess/go/vt/vtadmin/http/debug" ) var ( @@ -129,6 +130,9 @@ func main() { rootCmd.Flags().BoolVar(&httpOpts.EnableTracing, "http-tracing", false, "whether to enable tracing on the HTTP server") rootCmd.Flags().BoolVar(&httpOpts.DisableCompression, "http-no-compress", false, "whether to disable compression of HTTP API responses") + rootCmd.Flags().BoolVar(&httpOpts.DisableDebug, "http-no-debug", false, "whether to disable /debug/pprof/* and /debug/env HTTP endpoints") + rootCmd.Flags().Var(&debug.OmitEnv, "http-debug-omit-env", "name of an environment variable to omit from /debug/env, if http debug endpoints are enabled. specify multiple times to omit multiple env vars") + rootCmd.Flags().Var(&debug.SanitizeEnv, "http-debug-sanitize-env", "name of an environment variable to sanitize in /debug/env, if http debug endpoints are enabled. specify multiple times to sanitize multiple env vars") rootCmd.Flags().StringSliceVar(&httpOpts.CORSOrigins, "http-origin", []string{}, "repeated, comma-separated flag of allowed CORS origins. omit to disable CORS") rootCmd.Flags().StringVar(&httpOpts.ExperimentalOptions.TabletURLTmpl, "http-tablet-url-tmpl", diff --git a/go/vt/vtadmin/api.go b/go/vt/vtadmin/api.go index fbe9d5d1b0f..50e75bd4cfc 100644 --- a/go/vt/vtadmin/api.go +++ b/go/vt/vtadmin/api.go @@ -22,6 +22,7 @@ import ( stderrors "errors" "fmt" "net/http" + "net/http/pprof" stdsort "sort" "strings" "sync" @@ -40,6 +41,7 @@ import ( "vitess.io/vitess/go/vt/vtadmin/errors" "vitess.io/vitess/go/vt/vtadmin/grpcserver" vtadminhttp "vitess.io/vitess/go/vt/vtadmin/http" + "vitess.io/vitess/go/vt/vtadmin/http/debug" "vitess.io/vitess/go/vt/vtadmin/http/experimental" vthandlers "vitess.io/vitess/go/vt/vtadmin/http/handlers" "vitess.io/vitess/go/vt/vtadmin/sort" @@ -126,6 +128,19 @@ func NewAPI(clusters []*cluster.Cluster, opts grpcserver.Options, httpOpts vtadm experimentalRouter := router.PathPrefix("/experimental").Subrouter() experimentalRouter.HandleFunc("/tablet/{tablet}/debug/vars", httpAPI.Adapt(experimental.TabletDebugVarsPassthrough)).Name("API.TabletDebugVarsPassthrough") + if !httpOpts.DisableDebug { + // Due to the way net/http/pprof insists on registering its handlers, we + // have to put these on the root router, and not on the /debug prefixed + // subrouter, which would make way more sense, but alas. Additional + // debug routes should still go on the /debug subrouter, though. + serv.Router().HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + serv.Router().HandleFunc("/debug/pprof/profile", pprof.Profile) + serv.Router().HandleFunc("/debug/pprof/symbol", pprof.Symbol) + serv.Router().PathPrefix("/debug/pprof").HandlerFunc(pprof.Index) + debugRouter := serv.Router().PathPrefix("/debug").Subrouter() + debugRouter.HandleFunc("/env", debug.Env) + } + // Middlewares are executed in order of addition. Our ordering (all // middlewares being optional) is: // 1. CORS. CORS is a special case and is applied globally, the rest are applied only to the subrouter. diff --git a/go/vt/vtadmin/http/api.go b/go/vt/vtadmin/http/api.go index f2474efe06f..95253571b1f 100644 --- a/go/vt/vtadmin/http/api.go +++ b/go/vt/vtadmin/http/api.go @@ -36,7 +36,10 @@ type Options struct { // DisableCompression specifies whether to turn off gzip compression for API // endpoints. It is named as the negative (as opposed to EnableTracing) so // the zero value has compression enabled. - DisableCompression bool + DisableCompression bool + // DisableDebug specifies whether to omit the /debug/pprof/* and /debug/env + // routes. + DisableDebug bool ExperimentalOptions struct { TabletURLTmpl string } diff --git a/go/vt/vtadmin/http/debug/debug.go b/go/vt/vtadmin/http/debug/debug.go new file mode 100644 index 00000000000..189a3cb76f3 --- /dev/null +++ b/go/vt/vtadmin/http/debug/debug.go @@ -0,0 +1,102 @@ +/* +Copyright 2021 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package debug + +import ( + "fmt" + "net/http" + "os" + "sort" + "strings" + + "vitess.io/vitess/go/flagutil" +) + +var ( + // SanitizeEnv is the set of environment variables to sanitize their values + // in the Env http handler. + SanitizeEnv flagutil.StringSetFlag + // OmitEnv is the set of environment variables to omit entirely in the Env + // http handler. + OmitEnv flagutil.StringSetFlag +) + +const sanitized = "********" + +// Env responds with a plaintext listing of key=value pairs of the environment +// variables, sorted by key name. +// +// If a variable appears in OmitEnv, it is excluded entirely. If a variable +// appears in SanitizeEnv, its value is replaced with a sanitized string, +// including if there was no value set in the environment. +func Env(w http.ResponseWriter, r *http.Request) { + vars := readEnv() + + msg := &strings.Builder{} + for i, kv := range vars { + msg.WriteString(fmt.Sprintf("%s=%s", kv[0], kv[1])) + if i < len(vars)-1 { + msg.WriteByte('\n') + } + } + + w.Write([]byte(msg.String())) +} + +func readEnv() [][2]string { + env := os.Environ() + vars := make([][2]string, 0, len(env)) + + var key, value string + for _, ev := range env { + parts := strings.SplitN(ev, "=", 2) + switch len(parts) { + case 0: + key = ev + case 1: + key = parts[0] + default: + key = parts[0] + value = parts[1] + } + + if key == "" { + continue + } + + if OmitEnv.ToSet().Has(key) { + continue + } + + if SanitizeEnv.ToSet().Has(key) { + value = sanitized + } + + vars = append(vars, [2]string{ + key, + value, + }) + } + + // Sort by env var name, ascending. + sort.SliceStable(vars, func(i, j int) bool { + left, right := vars[i], vars[j] + return left[0] < right[0] + }) + + return vars +}