From ee9a2ac617b7042c19741837bd27d7283c41de13 Mon Sep 17 00:00:00 2001 From: Paulo Janotti Date: Thu, 22 Apr 2021 22:49:15 -0700 Subject: [PATCH] Expose initial and effective config for debugging purposes (Still incomplete, just the initial ideas) --- internal/configprovider/config_server.go | 145 ++++++++++++++++++ .../configprovider/config_source_provider.go | 9 ++ 2 files changed, 154 insertions(+) create mode 100644 internal/configprovider/config_server.go diff --git a/internal/configprovider/config_server.go b/internal/configprovider/config_server.go new file mode 100644 index 0000000000..f748eb34b3 --- /dev/null +++ b/internal/configprovider/config_server.go @@ -0,0 +1,145 @@ +// Copyright Splunk, Inc. +// Copyright The OpenTelemetry 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 configprovider + +import ( + "net" + "net/http" + "os" + "strings" + + "go.uber.org/zap" + "gopkg.in/yaml.v2" +) + +const ( + defaultEndpoint = "localhost:5555" +) + +type configServer struct { + logger *zap.Logger + initial map[string]interface{} + effective map[string]interface{} + server *http.Server + doneCh chan struct{} +} + +func newConfigServer(logger *zap.Logger, initial, effective map[string]interface{}) *configServer { + return &configServer{ + logger: logger, + initial: initial, + effective: effective, + } +} + +func (cs *configServer) start() error { + endpoint := defaultEndpoint + if portOverride, ok := os.LookupEnv("SPLUNK_CONFIG_SERVER_PORT"); ok { + if portOverride == "" { + // If explicitly set to empty do not start the server. + return nil + } + + endpoint = "localhost:" + portOverride + } + + listener, err := net.Listen("tcp", endpoint) + if err != nil { + return err + } + + mux := http.NewServeMux() + + initialHandleFunc, err := cs.muxHandleFunc(cs.initial) + if err != nil { + return err + } + mux.HandleFunc("/debug/configz/initial", initialHandleFunc) + + effectiveHandleFunc, err := cs.muxHandleFunc(simpleRedact(cs.effective)) + if err != nil { + return err + } + mux.HandleFunc("/debug/configz/effective", effectiveHandleFunc) + + cs.server = &http.Server{ + Handler: mux, + } + cs.doneCh = make(chan struct{}) + go func() { + defer close(cs.doneCh) + + if httpErr := cs.server.Serve(listener); httpErr != http.ErrServerClosed { + cs.logger.Error("config server error", zap.Error(err)) + } + }() + + return nil +} + +func (cs *configServer) shutdown() error { + var err error + if cs.server != nil { + err = cs.server.Close() + // If launched wait for Serve goroutine exit. + <-cs.doneCh + } + + return err +} + +func (cs *configServer) muxHandleFunc(config map[string]interface{}) (func(http.ResponseWriter, *http.Request), error) { + configYAML, err := yaml.Marshal(config) + if err != nil { + return nil, err + } + + return func(writer http.ResponseWriter, request *http.Request) { + _, _ = writer.Write(configYAML) + }, nil +} + +func simpleRedact(config map[string]interface{}) map[string]interface{} { + redactedConfig := make(map[string]interface{}) + for k, v := range config { + switch value := v.(type) { + case string: + if shouldRedactKey(k) { + v = "" + } + case map[string]interface{}: + v = simpleRedact(value) + } + + redactedConfig[k] = v + } + + return redactedConfig +} + +// shouldRedactKey applies a simple check to see if the contents of the given key +// should be redacted or not. +func shouldRedactKey(k string) bool { + fragments := []string{"access", "auth", "credential", "creds", "login", "password", "pwd", "user"} + + for _, fragment := range fragments { + if strings.Contains(k, fragment) { + return true + } + } + + return false +} diff --git a/internal/configprovider/config_source_provider.go b/internal/configprovider/config_source_provider.go index 9329e3eb48..56d74cb4e5 100644 --- a/internal/configprovider/config_source_provider.go +++ b/internal/configprovider/config_source_provider.go @@ -30,6 +30,7 @@ type errDuplicatedConfigSourceFactory struct{ error } type configSourceParserProvider struct { logger *zap.Logger csm *Manager + configServer *configServer pp parserprovider.ParserProvider appStartInfo component.ApplicationStartInfo factories []Factory @@ -69,6 +70,11 @@ func (c *configSourceParserProvider) Get() (*config.Parser, error) { return nil, err } + c.configServer = newConfigServer(c.logger, defaultParser.ToStringMap(), parser.ToStringMap()) + if err = c.configServer.start(); err != nil { + return nil, err + } + c.csm = csm return parser, nil } @@ -82,6 +88,9 @@ func (c *configSourceParserProvider) WatchForUpdate() error { // Close ends the watch for updates and closes the parser provider and respective // config sources. func (c *configSourceParserProvider) Close(ctx context.Context) error { + if c.configServer != nil { + _ = c.configServer.shutdown() + } return c.csm.Close(ctx) }