Skip to content

Commit

Permalink
Merge branch 'master' into ui/replication-status-discoverability
Browse files Browse the repository at this point in the history
  • Loading branch information
noelledaley committed Jun 2, 2020
2 parents 65e53f8 + a5de69f commit df031aa
Show file tree
Hide file tree
Showing 18 changed files with 949 additions and 36 deletions.
6 changes: 3 additions & 3 deletions command/operator_raft_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,21 @@ func (c *OperatorRaftJoinCommand) Flags() *FlagSets {
Name: "leader-ca-cert",
Target: &c.flagLeaderCACert,
Completion: complete.PredictNothing,
Usage: "CA cert to communicate with Raft leader.",
Usage: "CA cert to use when verifying the Raft leader certificate.",
})

f.StringVar(&StringVar{
Name: "leader-client-cert",
Target: &c.flagLeaderClientCert,
Completion: complete.PredictNothing,
Usage: "Client cert to to authenticate to Raft leader.",
Usage: "Client cert to use when authenticating with the Raft leader.",
})

f.StringVar(&StringVar{
Name: "leader-client-key",
Target: &c.flagLeaderClientKey,
Completion: complete.PredictNothing,
Usage: "Client key to to authenticate to Raft leader.",
Usage: "Client key to use when authenticating with the Raft leader.",
})

f.BoolVar(&BoolVar{
Expand Down
39 changes: 39 additions & 0 deletions helper/metricsutil/bucket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package metricsutil

import (
"sort"
"time"
)

var bucketBoundaries = []struct {
Value time.Duration
Label string
}{
{1 * time.Minute, "1m"},
{10 * time.Minute, "10m"},
{20 * time.Minute, "20m"},
{1 * time.Hour, "1h"},
{2 * time.Hour, "2h"},
{24 * time.Hour, "1d"},
{2 * 24 * time.Hour, "2d"},
{7 * 24 * time.Hour, "7d"},
{30 * 24 * time.Hour, "30d"},
}

const overflowBucket = "+Inf"

// TTLBucket computes the label to apply for a token TTL.
func TTLBucket(ttl time.Duration) string {
upperBound := sort.Search(
len(bucketBoundaries),
func(i int) bool {
return ttl <= bucketBoundaries[i].Value
},
)
if upperBound >= len(bucketBoundaries) {
return overflowBucket
} else {
return bucketBoundaries[upperBound].Label
}

}
28 changes: 28 additions & 0 deletions helper/metricsutil/bucket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package metricsutil

import (
"testing"
"time"
)

func TestTTLBucket_Lookup(t *testing.T) {
testCases := []struct {
Input time.Duration
Expected string
}{
{30 * time.Second, "1m"},
{0 * time.Second, "1m"},
{2 * time.Hour, "2h"},
{2*time.Hour - time.Second, "2h"},
{2*time.Hour + time.Second, "1d"},
{30 * 24 * time.Hour, "30d"},
{31 * 24 * time.Hour, "+Inf"},
}

for _, tc := range testCases {
bucket := TTLBucket(tc.Input)
if bucket != tc.Expected {
t.Errorf("Expected %q, got %q for duration %v.", tc.Expected, bucket, tc.Input)
}
}
}
17 changes: 17 additions & 0 deletions helper/metricsutil/wrapped_metrics.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package metricsutil

import (
"strings"
"time"

metrics "github.com/armon/go-metrics"
"github.com/hashicorp/vault/helper/namespace"
)

// ClusterMetricSink serves as a shim around go-metrics
Expand Down Expand Up @@ -68,3 +70,18 @@ func (m *ClusterMetricSink) SetDefaultClusterName(clusterName string) {
m.ClusterName = clusterName
}
}

// NamespaceLabel creates a metrics label for the given
// Namespace: root is "root"; others are path with the
// final '/' removed.
func NamespaceLabel(ns *namespace.Namespace) metrics.Label {
switch {
case ns == nil:
return metrics.Label{"namespace", "root"}
case ns.ID == namespace.RootNamespaceID:
return metrics.Label{"namespace", "root"}
default:
return metrics.Label{"namespace",
strings.Trim(ns.Path, "/")}
}
}
14 changes: 14 additions & 0 deletions vault/request_handling.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
multierror "github.com/hashicorp/go-multierror"
sockaddr "github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/sdk/framework"
Expand Down Expand Up @@ -1181,6 +1182,19 @@ func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (re
// Attach the display name, might be used by audit backends
req.DisplayName = auth.DisplayName

// Count the successful token creation
ttl_label := metricsutil.TTLBucket(tokenTTL)
c.metricSink.IncrCounterWithLabels(
[]string{"token", "creation"},
1,
[]metrics.Label{
metricsutil.NamespaceLabel(ns),
{"auth_method", req.MountType},
{"mount_point", req.MountPoint},
{"creation_ttl", ttl_label},
{"token_type", auth.TokenType.String()},
},
)
}

return resp, auth, routeErr
Expand Down
153 changes: 153 additions & 0 deletions vault/request_handling_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package vault

import (
"strings"
"testing"
"time"

"github.com/armon/go-metrics"
uuid "github.com/hashicorp/go-uuid"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
)
Expand Down Expand Up @@ -144,3 +147,153 @@ func TestRequestHandling_LoginWrapping(t *testing.T) {
t.Fatalf("bad: %#v", resp)
}
}

func labelsMatch(actual, expected map[string]string) bool {
for expected_label, expected_val := range expected {
if v, ok := actual[expected_label]; ok {
if v != expected_val {
return false
}
} else {
return false
}
}
return true
}

func checkCounter(t *testing.T, inmemSink *metrics.InmemSink, keyPrefix string, expectedLabels map[string]string) {
t.Helper()

intervals := inmemSink.Data()
if len(intervals) > 1 {
t.Skip("Detected interval crossing.")
}

var counter *metrics.SampledValue = nil
var labels map[string]string
for _, c := range intervals[0].Counters {
if !strings.HasPrefix(c.Name, keyPrefix) {
continue
}
counter = &c

labels = make(map[string]string)
for _, l := range counter.Labels {
labels[l.Name] = l.Value
}

// Distinguish between different label sets
if labelsMatch(labels, expectedLabels) {
break
}
}
if counter == nil {
t.Fatalf("No %q counter found with matching labels", keyPrefix)
}

if !labelsMatch(labels, expectedLabels) {
t.Errorf("No matching label set, found %v", labels)
}

if counter.Count != 1 {
t.Errorf("Counter number of samples %v is not 1.", counter.Count)
}

if counter.Sum != 1.0 {
t.Errorf("Counter sum %v is not 1.", counter.Sum)
}

}

func TestRequestHandling_LoginMetric(t *testing.T) {
core, _, root := TestCoreUnsealed(t)

if err := core.loadMounts(namespace.RootContext(nil)); err != nil {
t.Fatalf("err: %v", err)
}

core.credentialBackends["userpass"] = credUserpass.Factory

inmemSink := metrics.NewInmemSink(
1000000*time.Hour,
2000000*time.Hour)
core.metricSink = &metricsutil.ClusterMetricSink{
ClusterName: "test-cluster",
Sink: inmemSink,
}

// Setup mount
req := &logical.Request{
Path: "sys/auth/userpass",
ClientToken: root,
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"type": "userpass",
},
Connection: &logical.Connection{},
}
resp, err := core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}

// Create user
req.Path = "auth/userpass/users/test"
req.Data = map[string]interface{}{
"password": "foo",
"policies": "default",
}
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
t.Fatalf("bad: %#v", resp)
}

// Login with response wrapping
req = &logical.Request{
Path: "auth/userpass/login/test",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"password": "foo",
},
WrapInfo: &logical.RequestWrapInfo{
TTL: time.Duration(15 * time.Second),
},
Connection: &logical.Connection{},
}
resp, err = core.HandleRequest(namespace.RootContext(nil), req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp == nil {
t.Fatalf("bad: %v", resp)
}

// There should be two counters
checkCounter(t, inmemSink, "token.creation",
map[string]string{
"cluster": "test-cluster",
"namespace": "root",
"auth_method": "userpass",
"mount_point": "auth/userpass/",
"creation_ttl": "+Inf",
"token_type": "service",
},
)
checkCounter(t, inmemSink, "token.creation",
map[string]string{
"cluster": "test-cluster",
"namespace": "root",
"auth_method": "response_wrapping",
"mount_point": "auth/userpass/",
"creation_ttl": "1m",
"token_type": "service",
},
)

}
15 changes: 15 additions & 0 deletions vault/token_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-sockaddr"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/base62"
Expand Down Expand Up @@ -2716,6 +2717,20 @@ func (ts *TokenStore) handleCreateCommon(ctx context.Context, req *logical.Reque
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}

// Count the successful token creation.
ttl_label := metricsutil.TTLBucket(te.TTL)
ts.core.metricSink.IncrCounterWithLabels(
[]string{"token", "creation"},
1,
[]metrics.Label{
metricsutil.NamespaceLabel(ns),
{"auth_method", "token"},
{"mount_point", req.MountPoint}, // path, not accessor
{"creation_ttl", ttl_label},
{"token_type", tokenType.String()},
},
)

// Generate the response
resp.Auth = &logical.Auth{
NumUses: te.NumUses,
Expand Down

0 comments on commit df031aa

Please sign in to comment.