Skip to content

Commit

Permalink
Add new policy metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
biazmoreira committed Jun 6, 2023
1 parent dbe41c4 commit 4b32da4
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 0 deletions.
58 changes: 58 additions & 0 deletions vault/core_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ func (c *Core) emitMetricsActiveNode(stopCh chan struct{}) {
c.activeEntityGaugeCollector,
"",
},
{
[]string{"policy", "available", "count"},
[]metrics.Label{{"gauge", "number_policies_by_type"}},
c.availablePoliciesGaugeCollector,
"",
},
}

// Disable collection if configured, or if we're a performance standby
Expand Down Expand Up @@ -565,3 +571,55 @@ func (c *Core) inFlightReqGaugeMetric() {
// Adding a gauge metric to capture total number of inflight requests
c.metricSink.SetGaugeWithLabels([]string{"core", "in_flight_requests"}, float32(totalInFlightReq), nil)
}

func (c *Core) availablePoliciesGaugeCollector(ctx context.Context) ([]metricsutil.GaugeLabelValues, error) {
if c.policyStore == nil {
return []metricsutil.GaugeLabelValues{}, nil
}

c.stateLock.RLock()
policyStore := c.policyStore
c.stateLock.RUnlock()

ctx = namespace.RootContext(ctx)
namespaces := c.collectNamespaces()

policyTypes := []PolicyType{
PolicyTypeACL,
PolicyTypeRGP,
PolicyTypeEGP,
}
var values []metricsutil.GaugeLabelValues

filterDefaultPolicies := func(policies []string) []string {
var p []string
for _, s := range policies {
if s != "default" {
p = append(p, s)
}
}
return p
}

for _, pt := range policyTypes {
policies, err := policyStore.ListPoliciesForNamespaces(ctx, pt, namespaces)
if err != nil {
return []metricsutil.GaugeLabelValues{}, err
}

// Filter the "default" policies
policies = filterDefaultPolicies(policies)

if len(policies) > 0 {
v := metricsutil.GaugeLabelValues{}
v.Labels = []metricsutil.Label{{
"policy_type",
pt.String(),
}}
v.Value = float32(len(policies))
values = append(values, v)
}
}

return values, nil
}
84 changes: 84 additions & 0 deletions vault/core_metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
package vault

import (
"context"
"encoding/base64"
"errors"
"sort"
"strings"
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/armon/go-metrics"
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
"github.com/hashicorp/vault/helper/namespace"
Expand Down Expand Up @@ -350,3 +354,83 @@ func TestCoreMetrics_EntityGauges(t *testing.T) {
"mount_point": "auth/userpass/",
})
}

func TestCoreMetrics_AvailablePolicies(t *testing.T) {
aclPolicy := map[string]interface{}{
"policy": base64.StdEncoding.EncodeToString([]byte(`path "ns1/secret/foo/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}`)),
"name": "secret",
}

type pathPolicy struct {
Path string
Policy map[string]interface{}
}

tests := map[string]struct {
Policies []pathPolicy
ExpectedValues map[string]float32
}{
"single acl": {
Policies: []pathPolicy{
{
"sys/policy/secret", aclPolicy,
},
},
ExpectedValues: map[string]float32{
"acl": 1,
},
},
"multiple acl": {
Policies: []pathPolicy{
{
"sys/policy/secret", aclPolicy,
},
{
"sys/policy/secret2", aclPolicy,
},
},
ExpectedValues: map[string]float32{
"acl": 2,
},
},
}

for name, tst := range tests {
t.Run(name, func(t *testing.T) {
core, _, root := TestCoreUnsealed(t)

ctxRoot := namespace.RootContext(context.Background())

// Create policies
for _, p := range tst.Policies {
req := logical.TestRequest(t, logical.UpdateOperation, p.Path)
req.Data = p.Policy
req.ClientToken = root

resp, err := core.HandleRequest(ctxRoot, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp != nil {
logger.Info("expected nil response", resp)
t.Fatalf("expected nil response")
}
}

gValues, err := core.availablePoliciesGaugeCollector(ctxRoot)
if err != nil {
t.Fatalf("err: %v", err)
}

// Check the metrics values match the expected values
mgValues := make(map[string]float32, len(gValues))
for _, v := range gValues {
mgValues[v.Labels[0].Value] = v.Value
}

assert.EqualValues(t, tst.ExpectedValues, mgValues)
})
}
}
55 changes: 55 additions & 0 deletions vault/policy_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,61 @@ func (ps *PolicyStore) ListPolicies(ctx context.Context, policyType PolicyType)
return keys, err
}

// ListPoliciesForNamespace is used to list the available policies for the given namespace
func (ps *PolicyStore) listPoliciesForNamespace(ctx context.Context, policyType PolicyType, ns *namespace.Namespace) ([]string, error) {
var err error
var keys []string

// Get the appropriate view based on policy type and namespace
ctx = namespace.ContextWithNamespace(ctx, ns)

// Scan the view, since the policy names are the same as the
// key names.
switch policyType {
case PolicyTypeACL:
view := ps.getACLView(ns)
if view == nil {
return []string{}, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
}
keys, err = logical.CollectKeys(ctx, view)
case PolicyTypeRGP:
view := ps.getRGPView(ns)
if view == nil {
return []string{}, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
}
return logical.CollectKeys(ctx, view)
case PolicyTypeEGP:
view := ps.getEGPView(ns)
if view == nil {
return []string{}, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
}
return logical.CollectKeys(ctx, view)
default:
return nil, fmt.Errorf("unknown policy type %q", policyType)
}

// We only have non-assignable ACL policies at the moment
keys = strutil.Difference(keys, nonAssignablePolicies, false)

return keys, err
}

// ListPoliciesForNamespaces is used to list the available policies for the given namespaces
func (ps *PolicyStore) ListPoliciesForNamespaces(ctx context.Context, policyType PolicyType, ns []*namespace.Namespace) ([]string, error) {
var err error
var keys []string

for _, nspace := range ns {
ks, err := ps.listPoliciesForNamespace(ctx, policyType, nspace)
if err != nil {
return []string{}, err
}
keys = append(keys, ks...)
}

return keys, err
}

// DeletePolicy is used to delete the named policy
func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType PolicyType) error {
return ps.switchedDeletePolicy(ctx, name, policyType, true, false)
Expand Down
34 changes: 34 additions & 0 deletions vault/policy_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,3 +319,37 @@ func TestDefaultPolicy(t *testing.T) {
})
}
}

// TestPolicyStore_ListPoliciesForNamespaces tests the ListPoliciesForNamespaces function, which should return a list of policies for a given list of namespaces.
func TestPolicyStore_ListPoliciesForNamespaces(t *testing.T) {
_, ps := mockPolicyWithCore(t, false)

ctxRoot := namespace.RootContext(context.Background())
rootNs := namespace.RootNamespace

parsedPolicy, _ := ParseACLPolicy(rootNs, aclPolicy)

err := ps.SetPolicy(ctxRoot, parsedPolicy)
if err != nil {
t.Fatalf("err: %v", err)
}

// Get should work
pResult, err := ps.GetPolicy(ctxRoot, "dev", PolicyTypeACL)
if err != nil {
t.Fatalf("err: %v", err)
}
if !reflect.DeepEqual(pResult, parsedPolicy) {
t.Fatalf("bad: %v", pResult)
}

out, err := ps.ListPoliciesForNamespaces(ctxRoot, PolicyTypeACL, []*namespace.Namespace{rootNs})
if err != nil {
t.Fatalf("err: %v", err)
}

expectedResult := []string{"default", "dev"}
if !reflect.DeepEqual(expectedResult, out) {
t.Fatalf("expected: %v\ngot: %v", expectedResult, out)
}
}

0 comments on commit 4b32da4

Please sign in to comment.