mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
Add new policy metrics (#21010)
* Add new policy metrics * Add changelog entry * Change policy wording
This commit is contained in:
3
changelog/21010.txt
Normal file
3
changelog/21010.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
core: Add a new periodic metric to track the number of available policies, `vault.policy.configured.count`.
|
||||
```
|
||||
@@ -301,6 +301,12 @@ func (c *Core) emitMetricsActiveNode(stopCh chan struct{}) {
|
||||
c.activeEntityGaugeCollector,
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]string{"policy", "configured", "count"},
|
||||
[]metrics.Label{{"gauge", "number_policies_by_type"}},
|
||||
c.configuredPoliciesGaugeCollector,
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
// Disable collection if configured, or if we're a performance standby
|
||||
@@ -565,3 +571,41 @@ 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)
|
||||
}
|
||||
|
||||
// configuredPoliciesGaugeCollector is used to collect gauge label values for the `vault.policy.configured.count` metric
|
||||
func (c *Core) configuredPoliciesGaugeCollector(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
|
||||
|
||||
for _, pt := range policyTypes {
|
||||
policies, err := policyStore.policiesByNamespaces(ctx, pt, namespaces)
|
||||
if err != nil {
|
||||
return []metricsutil.GaugeLabelValues{}, err
|
||||
}
|
||||
|
||||
v := metricsutil.GaugeLabelValues{}
|
||||
v.Labels = []metricsutil.Label{{
|
||||
"policy_type",
|
||||
pt.String(),
|
||||
}}
|
||||
v.Value = float32(len(policies))
|
||||
values = append(values, v)
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -350,3 +354,90 @@ func TestCoreMetrics_EntityGauges(t *testing.T) {
|
||||
"mount_point": "auth/userpass/",
|
||||
})
|
||||
}
|
||||
|
||||
// TestCoreMetrics_AvailablePolicies tests the that available metrics are getting correctly collected when the availablePoliciesGaugeCollector function is invoked
|
||||
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{
|
||||
// The "default" policy will always be included
|
||||
"acl": 2,
|
||||
"egp": 0,
|
||||
"rgp": 0,
|
||||
},
|
||||
},
|
||||
"multiple acl": {
|
||||
Policies: []pathPolicy{
|
||||
{
|
||||
"sys/policy/secret", aclPolicy,
|
||||
},
|
||||
{
|
||||
"sys/policy/secret2", aclPolicy,
|
||||
},
|
||||
},
|
||||
ExpectedValues: map[string]float32{
|
||||
// The "default" policy will always be included
|
||||
"acl": 3,
|
||||
"egp": 0,
|
||||
"rgp": 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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.configuredPoliciesGaugeCollector(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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +649,60 @@ func (ps *PolicyStore) ListPolicies(ctx context.Context, policyType PolicyType)
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// policiesByNamespace is used to list the available policies for the given namespace
|
||||
func (ps *PolicyStore) policiesByNamespace(ctx context.Context, policyType PolicyType, ns *namespace.Namespace) ([]string, error) {
|
||||
var err error
|
||||
var keys []string
|
||||
var view *BarrierView
|
||||
|
||||
// Scan the view, since the policy names are the same as the
|
||||
// key names.
|
||||
switch policyType {
|
||||
case PolicyTypeACL:
|
||||
view = ps.getACLView(ns)
|
||||
case PolicyTypeRGP:
|
||||
view = ps.getRGPView(ns)
|
||||
case PolicyTypeEGP:
|
||||
view = ps.getEGPView(ns)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown policy type %q", policyType)
|
||||
}
|
||||
|
||||
if view == nil {
|
||||
return nil, fmt.Errorf("unable to get the barrier subview for policy type %q", policyType)
|
||||
}
|
||||
|
||||
// Get the appropriate view based on policy type and namespace
|
||||
ctx = namespace.ContextWithNamespace(ctx, ns)
|
||||
keys, err = logical.CollectKeys(ctx, view)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if policyType == PolicyTypeACL {
|
||||
// We only have non-assignable ACL policies at the moment
|
||||
keys = strutil.Difference(keys, nonAssignablePolicies, false)
|
||||
}
|
||||
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// policiesByNamespaces is used to list the available policies for the given namespaces
|
||||
func (ps *PolicyStore) policiesByNamespaces(ctx context.Context, policyType PolicyType, ns []*namespace.Namespace) ([]string, error) {
|
||||
var err error
|
||||
var keys []string
|
||||
|
||||
for _, nspace := range ns {
|
||||
ks, err := ps.policiesByNamespace(ctx, policyType, nspace)
|
||||
if err != nil {
|
||||
return nil, 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)
|
||||
|
||||
@@ -319,3 +319,37 @@ func TestDefaultPolicy(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPolicyStore_PoliciesByNamespaces tests the policiesByNamespaces function, which should return a slice of policy names for a given slice of namespaces.
|
||||
func TestPolicyStore_PoliciesByNamespaces(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.policiesByNamespaces(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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user