app/vmagent: substitute -remoteWrite.url with secret-url value in logs, since it may contain sensitive info such as passwords or auth tokens

Pass `-remoteWrite.showURL` command-line flag in order to see real `-remoteWrite.url` values in logs and at `/metrics` page.

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/773
This commit is contained in:
Aliaksandr Valialkin 2020-09-16 22:34:01 +03:00
parent 9f79bcf64a
commit ab53cb6f7b
7 changed files with 63 additions and 48 deletions

View file

@ -228,6 +228,10 @@ If you have suggestions, improvements or found a bug - feel free to open an issu
The directory can grow large when remote storage is unavailable for extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set. The directory can grow large when remote storage is unavailable for extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set.
If you don't want to send all the data from the directory to remote storage, simply stop `vmagent` and delete the directory. If you don't want to send all the data from the directory to remote storage, simply stop `vmagent` and delete the directory.
* By default `vmagent` masks `-remoteWrite.url` with `secret-url` values in logs and at `/metrics` page because
the url may contain sensitive information such as auth tokens or passwords.
Pass `-remoteWrite.showURL` command-line flag when starting `vmagent` in order to see all the valid urls.
* If you see `skipping duplicate scrape target with identical labels` errors when scraping Kubernetes pods, then it is likely these pods listen multiple ports * If you see `skipping duplicate scrape target with identical labels` errors when scraping Kubernetes pods, then it is likely these pods listen multiple ports
or they use init container. or they use init container.

View file

@ -44,7 +44,7 @@ var (
) )
type client struct { type client struct {
urlLabelValue string sanitizedURL string
remoteWriteURL string remoteWriteURL string
authHeader string authHeader string
fq *persistentqueue.FastQueue fq *persistentqueue.FastQueue
@ -59,7 +59,7 @@ type client struct {
stopCh chan struct{} stopCh chan struct{}
} }
func newClient(argIdx int, remoteWriteURL, urlLabelValue string, fq *persistentqueue.FastQueue, concurrency int) *client { func newClient(argIdx int, remoteWriteURL, sanitizedURL string, fq *persistentqueue.FastQueue, concurrency int) *client {
tlsCfg, err := getTLSConfig(argIdx) tlsCfg, err := getTLSConfig(argIdx)
if err != nil { if err != nil {
logger.Panicf("FATAL: cannot initialize TLS config: %s", err) logger.Panicf("FATAL: cannot initialize TLS config: %s", err)
@ -101,7 +101,7 @@ func newClient(argIdx int, remoteWriteURL, urlLabelValue string, fq *persistentq
authHeader = "Bearer " + token authHeader = "Bearer " + token
} }
c := &client{ c := &client{
urlLabelValue: urlLabelValue, sanitizedURL: sanitizedURL,
remoteWriteURL: remoteWriteURL, remoteWriteURL: remoteWriteURL,
authHeader: authHeader, authHeader: authHeader,
fq: fq, fq: fq,
@ -111,10 +111,10 @@ func newClient(argIdx int, remoteWriteURL, urlLabelValue string, fq *persistentq
}, },
stopCh: make(chan struct{}), stopCh: make(chan struct{}),
} }
c.requestDuration = metrics.GetOrCreateHistogram(fmt.Sprintf(`vmagent_remotewrite_duration_seconds{url=%q}`, c.urlLabelValue)) c.requestDuration = metrics.GetOrCreateHistogram(fmt.Sprintf(`vmagent_remotewrite_duration_seconds{url=%q}`, c.sanitizedURL))
c.requestsOKCount = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_requests_total{url=%q, status_code="2XX"}`, c.urlLabelValue)) c.requestsOKCount = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_requests_total{url=%q, status_code="2XX"}`, c.sanitizedURL))
c.errorsCount = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_errors_total{url=%q}`, c.urlLabelValue)) c.errorsCount = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_errors_total{url=%q}`, c.sanitizedURL))
c.retriesCount = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_retries_count_total{url=%q}`, c.urlLabelValue)) c.retriesCount = metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_retries_count_total{url=%q}`, c.sanitizedURL))
for i := 0; i < concurrency; i++ { for i := 0; i < concurrency; i++ {
c.wg.Add(1) c.wg.Add(1)
go func() { go func() {
@ -122,14 +122,14 @@ func newClient(argIdx int, remoteWriteURL, urlLabelValue string, fq *persistentq
c.runWorker() c.runWorker()
}() }()
} }
logger.Infof("initialized client for -remoteWrite.url=%q", c.remoteWriteURL) logger.Infof("initialized client for -remoteWrite.url=%q", c.sanitizedURL)
return c return c
} }
func (c *client) MustStop() { func (c *client) MustStop() {
close(c.stopCh) close(c.stopCh)
c.wg.Wait() c.wg.Wait()
logger.Infof("stopped client for -remoteWrite.url=%q", c.remoteWriteURL) logger.Infof("stopped client for -remoteWrite.url=%q", c.sanitizedURL)
} }
func getTLSConfig(argIdx int) (*tls.Config, error) { func getTLSConfig(argIdx int) (*tls.Config, error) {
@ -176,7 +176,7 @@ func (c *client) runWorker() {
// The block has been sent successfully. // The block has been sent successfully.
case <-time.After(graceDuration): case <-time.After(graceDuration):
logger.Errorf("couldn't sent block with size %d bytes to %q in %.3f seconds during shutdown; dropping it", logger.Errorf("couldn't sent block with size %d bytes to %q in %.3f seconds during shutdown; dropping it",
len(block), c.remoteWriteURL, graceDuration.Seconds()) len(block), c.sanitizedURL, graceDuration.Seconds())
} }
return return
} }
@ -190,7 +190,7 @@ func (c *client) sendBlock(block []byte) {
again: again:
req, err := http.NewRequest("POST", c.remoteWriteURL, bytes.NewBuffer(block)) req, err := http.NewRequest("POST", c.remoteWriteURL, bytes.NewBuffer(block))
if err != nil { if err != nil {
logger.Panicf("BUG: unexected error from http.NewRequest(%q): %s", c.remoteWriteURL, err) logger.Panicf("BUG: unexected error from http.NewRequest(%q): %s", c.sanitizedURL, err)
} }
h := req.Header h := req.Header
h.Set("User-Agent", "vmagent") h.Set("User-Agent", "vmagent")
@ -211,7 +211,7 @@ again:
retryDuration = time.Minute retryDuration = time.Minute
} }
logger.Errorf("couldn't send a block with size %d bytes to %q: %s; re-sending the block in %.3f seconds", logger.Errorf("couldn't send a block with size %d bytes to %q: %s; re-sending the block in %.3f seconds",
len(block), c.remoteWriteURL, err, retryDuration.Seconds()) len(block), c.sanitizedURL, err, retryDuration.Seconds())
t := time.NewTimer(retryDuration) t := time.NewTimer(retryDuration)
select { select {
case <-c.stopCh: case <-c.stopCh:
@ -231,7 +231,7 @@ again:
// Unexpected status code returned // Unexpected status code returned
retriesCount++ retriesCount++
metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_requests_total{url=%q, status_code="%d"}`, c.urlLabelValue, statusCode)).Inc() metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_requests_total{url=%q, status_code="%d"}`, c.sanitizedURL, statusCode)).Inc()
retryDuration *= 2 retryDuration *= 2
if retryDuration > time.Minute { if retryDuration > time.Minute {
retryDuration = time.Minute retryDuration = time.Minute
@ -239,10 +239,10 @@ again:
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
_ = resp.Body.Close() _ = resp.Body.Close()
if err != nil { if err != nil {
logger.Errorf("cannot read response body from %q during retry #%d: %s", c.remoteWriteURL, retriesCount, err) logger.Errorf("cannot read response body from %q during retry #%d: %s", c.sanitizedURL, retriesCount, err)
} else { } else {
logger.Errorf("unexpected status code received after sending a block with size %d bytes to %q during retry #%d: %d; response body=%q; "+ logger.Errorf("unexpected status code received after sending a block with size %d bytes to %q during retry #%d: %d; response body=%q; "+
"re-sending the block in %.3f seconds", len(block), c.remoteWriteURL, retriesCount, statusCode, body, retryDuration.Seconds()) "re-sending the block in %.3f seconds", len(block), c.sanitizedURL, retriesCount, statusCode, body, retryDuration.Seconds())
} }
t := time.NewTimer(retryDuration) t := time.NewTimer(retryDuration)
select { select {

View file

@ -9,7 +9,6 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal" "github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil" "github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/httpserver"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger" "github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory" "github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue" "github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
@ -63,7 +62,7 @@ func Init() {
} }
if !*showRemoteWriteURL { if !*showRemoteWriteURL {
// remoteWrite.url can contain authentication codes, so hide it at `/metrics` output. // remoteWrite.url can contain authentication codes, so hide it at `/metrics` output.
httpserver.RegisterSecretFlag("remoteWrite.url") flagutil.RegisterSecretFlag("remoteWrite.url")
} }
initLabelsGlobal() initLabelsGlobal()
rcs, err := loadRelabelConfigs() rcs, err := loadRelabelConfigs()
@ -83,11 +82,11 @@ func Init() {
maxInmemoryBlocks = 2 maxInmemoryBlocks = 2
} }
for i, remoteWriteURL := range *remoteWriteURLs { for i, remoteWriteURL := range *remoteWriteURLs {
urlLabelValue := fmt.Sprintf("secret-url-%d", i+1) sanitizedURL := fmt.Sprintf("%d:secret-url", i+1)
if *showRemoteWriteURL { if *showRemoteWriteURL {
urlLabelValue = fmt.Sprintf("%d:%s", i+1, remoteWriteURL) sanitizedURL = fmt.Sprintf("%d:%s", i+1, remoteWriteURL)
} }
rwctx := newRemoteWriteCtx(i, remoteWriteURL, maxInmemoryBlocks, urlLabelValue) rwctx := newRemoteWriteCtx(i, remoteWriteURL, maxInmemoryBlocks, sanitizedURL)
rwctxs = append(rwctxs, rwctx) rwctxs = append(rwctxs, rwctx)
} }
@ -190,17 +189,17 @@ type remoteWriteCtx struct {
relabelMetricsDropped *metrics.Counter relabelMetricsDropped *metrics.Counter
} }
func newRemoteWriteCtx(argIdx int, remoteWriteURL string, maxInmemoryBlocks int, urlLabelValue string) *remoteWriteCtx { func newRemoteWriteCtx(argIdx int, remoteWriteURL string, maxInmemoryBlocks int, sanitizedURL string) *remoteWriteCtx {
h := xxhash.Sum64([]byte(remoteWriteURL)) h := xxhash.Sum64([]byte(remoteWriteURL))
path := fmt.Sprintf("%s/persistent-queue/%d_%016X", *tmpDataPath, argIdx+1, h) path := fmt.Sprintf("%s/persistent-queue/%d_%016X", *tmpDataPath, argIdx+1, h)
fq := persistentqueue.MustOpenFastQueue(path, remoteWriteURL, maxInmemoryBlocks, maxPendingBytesPerURL.N) fq := persistentqueue.MustOpenFastQueue(path, sanitizedURL, maxInmemoryBlocks, maxPendingBytesPerURL.N)
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vmagent_remotewrite_pending_data_bytes{path=%q, url=%q}`, path, urlLabelValue), func() float64 { _ = metrics.GetOrCreateGauge(fmt.Sprintf(`vmagent_remotewrite_pending_data_bytes{path=%q, url=%q}`, path, sanitizedURL), func() float64 {
return float64(fq.GetPendingBytes()) return float64(fq.GetPendingBytes())
}) })
_ = metrics.GetOrCreateGauge(fmt.Sprintf(`vmagent_remotewrite_pending_inmemory_blocks{path=%q, url=%q}`, path, urlLabelValue), func() float64 { _ = metrics.GetOrCreateGauge(fmt.Sprintf(`vmagent_remotewrite_pending_inmemory_blocks{path=%q, url=%q}`, path, sanitizedURL), func() float64 {
return float64(fq.GetInmemoryQueueLen()) return float64(fq.GetInmemoryQueueLen())
}) })
c := newClient(argIdx, remoteWriteURL, urlLabelValue, fq, *queues) c := newClient(argIdx, remoteWriteURL, sanitizedURL, fq, *queues)
pss := make([]*pendingSeries, *queues) pss := make([]*pendingSeries, *queues)
for i := range pss { for i := range pss {
pss[i] = newPendingSeries(fq.MustWriteBlock) pss[i] = newPendingSeries(fq.MustWriteBlock)
@ -211,7 +210,7 @@ func newRemoteWriteCtx(argIdx int, remoteWriteURL string, maxInmemoryBlocks int,
c: c, c: c,
pss: pss, pss: pss,
relabelMetricsDropped: metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_relabel_metrics_dropped_total{path=%q, url=%q}`, path, urlLabelValue)), relabelMetricsDropped: metrics.GetOrCreateCounter(fmt.Sprintf(`vmagent_remotewrite_relabel_metrics_dropped_total{path=%q, url=%q}`, path, sanitizedURL)),
} }
} }

View file

@ -228,6 +228,10 @@ If you have suggestions, improvements or found a bug - feel free to open an issu
The directory can grow large when remote storage is unavailable for extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set. The directory can grow large when remote storage is unavailable for extended periods of time and if `-remoteWrite.maxDiskUsagePerURL` isn't set.
If you don't want to send all the data from the directory to remote storage, simply stop `vmagent` and delete the directory. If you don't want to send all the data from the directory to remote storage, simply stop `vmagent` and delete the directory.
* By default `vmagent` masks `-remoteWrite.url` with `secret-url` values in logs and at `/metrics` page because
the url may contain sensitive information such as auth tokens or passwords.
Pass `-remoteWrite.showURL` command-line flag when starting `vmagent` in order to see all the valid urls.
* If you see `skipping duplicate scrape target with identical labels` errors when scraping Kubernetes pods, then it is likely these pods listen multiple ports * If you see `skipping duplicate scrape target with identical labels` errors when scraping Kubernetes pods, then it is likely these pods listen multiple ports
or they use init container. or they use init container.

26
lib/flagutil/secret.go Normal file
View file

@ -0,0 +1,26 @@
package flagutil
import (
"strings"
)
// RegisterSecretFlag registers flagName as secret.
//
// This function must be called before starting logging.
// It cannot be called from concurrent goroutines.
//
// Secret flags aren't exported at `/metrics` page.
func RegisterSecretFlag(flagName string) {
lname := strings.ToLower(flagName)
secretFlags[lname] = true
}
var secretFlags = make(map[string]bool)
// IsSecretFlag returns true of s contains flag name with secret value, which shouldn't be exposed.
func IsSecretFlag(s string) bool {
if strings.Contains(s, "pass") || strings.Contains(s, "key") || strings.Contains(s, "secret") || strings.Contains(s, "token") {
return true
}
return secretFlags[s]
}

View file

@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo" "github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/memory" "github.com/VictoriaMetrics/VictoriaMetrics/lib/memory"
"github.com/VictoriaMetrics/metrics" "github.com/VictoriaMetrics/metrics"
) )
@ -27,7 +28,7 @@ func WritePrometheusMetrics(w io.Writer) {
flag.VisitAll(func(f *flag.Flag) { flag.VisitAll(func(f *flag.Flag) {
lname := strings.ToLower(f.Name) lname := strings.ToLower(f.Name)
value := f.Value.String() value := f.Value.String()
if isSecretFlag(lname) { if flagutil.IsSecretFlag(lname) {
// Do not expose passwords and keys to prometheus. // Do not expose passwords and keys to prometheus.
value = "secret" value = "secret"
} }
@ -36,23 +37,3 @@ func WritePrometheusMetrics(w io.Writer) {
} }
var startTime = time.Now() var startTime = time.Now()
// RegisterSecretFlag registers flagName as secret.
//
// This function must be called before starting httpserver.
// It cannot be called from concurrent goroutines.
//
// Secret flags aren't exported at `/metrics` page.
func RegisterSecretFlag(flagName string) {
lname := strings.ToLower(flagName)
secretFlags[lname] = true
}
var secretFlags = make(map[string]bool)
func isSecretFlag(s string) bool {
if strings.Contains(s, "pass") || strings.Contains(s, "key") || strings.Contains(s, "secret") || strings.Contains(s, "token") {
return true
}
return secretFlags[s]
}

View file

@ -5,6 +5,7 @@ import (
"strings" "strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo" "github.com/VictoriaMetrics/VictoriaMetrics/lib/buildinfo"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/flagutil"
) )
func logAllFlags() { func logAllFlags() {
@ -13,7 +14,7 @@ func logAllFlags() {
flag.VisitAll(func(f *flag.Flag) { flag.VisitAll(func(f *flag.Flag) {
lname := strings.ToLower(f.Name) lname := strings.ToLower(f.Name)
value := f.Value.String() value := f.Value.String()
if strings.Contains(lname, "pass") || strings.Contains(lname, "key") || strings.Contains(lname, "secret") { if flagutil.IsSecretFlag(lname) {
// Do not expose passwords and keys to prometheus. // Do not expose passwords and keys to prometheus.
value = "secret" value = "secret"
} }