Add config parameter to allow unauthenticated metrics access (#7550)

* Implement config parameter to allow unathenticated metricss access

* Add unit test for unauthenticated metrics access parameter

* go mod tidy
This commit is contained in:
Michel Vocks 2019-10-04 09:29:51 +02:00 committed by GitHub
parent 8239612352
commit a7a6dd55a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 24 deletions

View File

@ -20,25 +20,22 @@ import (
"sync"
"time"
"golang.org/x/net/http/httpproxy"
"github.com/hashicorp/vault/helper/metricsutil"
monitoring "cloud.google.com/go/monitoring/apiv3"
metrics "github.com/armon/go-metrics"
"github.com/armon/go-metrics"
"github.com/armon/go-metrics/circonus"
"github.com/armon/go-metrics/datadog"
"github.com/armon/go-metrics/prometheus"
stackdriver "github.com/google/go-metrics-stackdriver"
"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
multierror "github.com/hashicorp/go-multierror"
sockaddr "github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/audit"
"github.com/hashicorp/vault/command/server"
serverseal "github.com/hashicorp/vault/command/server/seal"
"github.com/hashicorp/vault/helper/builtinplugins"
gatedwriter "github.com/hashicorp/vault/helper/gated-writer"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/reload"
vaulthttp "github.com/hashicorp/vault/http"
@ -54,8 +51,9 @@ import (
vaultseal "github.com/hashicorp/vault/vault/seal"
shamirseal "github.com/hashicorp/vault/vault/seal/shamir"
"github.com/mitchellh/cli"
testing "github.com/mitchellh/go-testing-interface"
"github.com/mitchellh/go-testing-interface"
"github.com/posener/complete"
"golang.org/x/net/http/httpproxy"
"google.golang.org/api/option"
"google.golang.org/grpc/grpclog"
)
@ -125,9 +123,10 @@ type ServerCommand struct {
type ServerListener struct {
net.Listener
config map[string]interface{}
maxRequestSize int64
maxRequestDuration time.Duration
config map[string]interface{}
maxRequestSize int64
maxRequestDuration time.Duration
unauthenticatedMetricsAccess bool
}
func (c *ServerCommand) Synopsis() string {
@ -988,11 +987,31 @@ CLUSTER_SYNTHESIS_COMPLETE:
}
props["max_request_duration"] = fmt.Sprintf("%s", maxRequestDuration.String())
var unauthenticatedMetricsAccess bool
if telemetryRaw, ok := lnConfig.Config["telemetry"]; ok {
telemetry, ok := telemetryRaw.([]map[string]interface{})
if !ok {
c.UI.Error(fmt.Sprintf("Could not parse telemetry sink value %v", telemetryRaw))
return 1
}
for _, item := range telemetry {
if valRaw, ok := item["unauthenticated_metrics_access"]; ok {
unauthenticatedMetricsAccess, err = parseutil.ParseBool(valRaw)
if err != nil {
c.UI.Error(fmt.Sprintf("Could not parse unauthenticated_metrics_access value %v", valRaw))
return 1
}
}
}
}
lns = append(lns, ServerListener{
Listener: ln,
config: lnConfig.Config,
maxRequestSize: maxRequestSize,
maxRequestDuration: maxRequestDuration,
Listener: ln,
config: lnConfig.Config,
maxRequestSize: maxRequestSize,
maxRequestDuration: maxRequestDuration,
unauthenticatedMetricsAccess: unauthenticatedMetricsAccess,
})
// Store the listener props for output later
@ -1229,10 +1248,11 @@ CLUSTER_SYNTHESIS_COMPLETE:
// Initialize the HTTP servers
for _, ln := range lns {
handler := vaulthttp.Handler(&vault.HandlerProperties{
Core: core,
MaxRequestSize: ln.maxRequestSize,
MaxRequestDuration: ln.maxRequestDuration,
DisablePrintableCheck: config.DisablePrintableCheck,
Core: core,
MaxRequestSize: ln.maxRequestSize,
MaxRequestDuration: ln.maxRequestDuration,
DisablePrintableCheck: config.DisablePrintableCheck,
UnauthenticatedMetricsAccess: ln.unauthenticatedMetricsAccess,
})
// We perform validation on the config earlier, we can just cast here

1
go.mod
View File

@ -91,7 +91,6 @@ require (
github.com/joyent/triton-go v0.0.0-20190112182421-51ffac552869
github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f
github.com/kr/pretty v0.1.0
github.com/kr/pty v1.1.3 // indirect
github.com/kr/text v0.1.0
github.com/lib/pq v1.2.0
github.com/mattn/go-colorable v0.1.2

View File

@ -147,6 +147,11 @@ func Handler(props *vault.HandlerProperties) http.Handler {
mux.Handle("/", handleUIRedirect())
}
// Register metrics path without authentication if enabled
if props.UnauthenticatedMetricsAccess {
mux.Handle("/v1/sys/metrics", handleMetricsUnauthenticated(core))
}
additionalRoutes(mux, core)
// Wrap the handler in another handler to trigger all help paths.

32
http/sys_metrics.go Normal file
View File

@ -0,0 +1,32 @@
package http
import (
"net/http"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)
func handleMetricsUnauthenticated(core *vault.Core) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := &logical.Request{Headers: r.Header}
format := r.Form.Get("format")
if format == "" {
format = metricsutil.FormatFromRequest(req)
}
// Define response
resp, err := core.MetricsHelper().ResponseForFormat(format)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
// Manually extract the logical response and send back the information
w.WriteHeader(resp.Data[logical.HTTPStatusCode].(int))
w.Header().Set("Content-Type", resp.Data[logical.HTTPContentType].(string))
w.Write(resp.Data[logical.HTTPRawBody].([]byte))
})
}

50
http/sys_metrics_test.go Normal file
View File

@ -0,0 +1,50 @@
package http
import (
"testing"
"time"
"github.com/armon/go-metrics"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/vault"
)
func TestSysMetricsUnauthenticated(t *testing.T) {
inm := metrics.NewInmemSink(10*time.Second, time.Minute)
metrics.DefaultInmemSignal(inm)
conf := &vault.CoreConfig{
BuiltinRegistry: vault.NewMockBuiltinRegistry(),
MetricsHelper: metricsutil.NewMetricsHelper(inm, false),
}
core, _, token := vault.TestCoreUnsealedWithConfig(t, conf)
ln, addr := TestServer(t, core)
TestServerAuth(t, addr, token)
// Default: Only authenticated access
resp := testHttpGet(t, "", addr+"/v1/sys/metrics")
testResponseStatus(t, resp, 400)
resp = testHttpGet(t, token, addr+"/v1/sys/metrics")
testResponseStatus(t, resp, 200)
// Close listener
ln.Close()
// Setup new custom listener with unauthenticated metrics access
ln, addr = TestListener(t)
props := &vault.HandlerProperties{
Core: core,
MaxRequestSize: DefaultMaxRequestSize,
UnauthenticatedMetricsAccess: true,
}
TestServerWithListenerAndProperties(t, ln, addr, core, props)
defer ln.Close()
TestServerAuth(t, addr, token)
// Test without token
resp = testHttpGet(t, "", addr+"/v1/sys/metrics")
testResponseStatus(t, resp, 200)
// Should also work with token
resp = testHttpGet(t, token, addr+"/v1/sys/metrics")
testResponseStatus(t, resp, 200)
}

View File

@ -1979,6 +1979,12 @@ func (c *Core) SetLogLevel(level log.Level) {
}
}
// MetricsHelper returns the global metrics helper which allows external
// packages to access Vault's internal metrics.
func (c *Core) MetricsHelper() *metricsutil.MetricsHelper {
return c.metricsHelper
}
// BuiltinRegistry is an interface that allows the "vault" package to use
// the registry of builtin plugins without getting an import cycle. It
// also allows for mocking the registry easily.

View File

@ -39,10 +39,11 @@ var (
// HandlerProperties is used to seed configuration into a vaulthttp.Handler.
// It's in this package to avoid a circular dependency
type HandlerProperties struct {
Core *Core
MaxRequestSize int64
MaxRequestDuration time.Duration
DisablePrintableCheck bool
Core *Core
MaxRequestSize int64
MaxRequestDuration time.Duration
DisablePrintableCheck bool
UnauthenticatedMetricsAccess bool
}
// fetchEntityAndDerivedPolicies returns the entity object for the given entity

View File

@ -156,6 +156,7 @@ func TestCoreWithSealAndUI(t testing.T, opts *CoreConfig) *Core {
conf.Seal = opts.Seal
conf.LicensingConfig = opts.LicensingConfig
conf.DisableKeyEncodingChecks = opts.DisableKeyEncodingChecks
conf.MetricsHelper = opts.MetricsHelper
if opts.Logger != nil {
conf.Logger = opts.Logger