Skip to content

Commit

Permalink
Add config parameter to allow unauthenticated metrics access (#7550)
Browse files Browse the repository at this point in the history
* Implement config parameter to allow unathenticated metricss access

* Add unit test for unauthenticated metrics access parameter

* go mod tidy
  • Loading branch information
michelvocks authored Oct 4, 2019
1 parent 338afe2 commit 993a1ae
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 24 deletions.
58 changes: 39 additions & 19 deletions command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 32 additions & 0 deletions http/sys_metrics.go
Original file line number Diff line number Diff line change
@@ -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 changes: 50 additions & 0 deletions http/sys_metrics_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
6 changes: 6 additions & 0 deletions vault/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
9 changes: 5 additions & 4 deletions vault/request_handling.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions vault/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 993a1ae

Please sign in to comment.