mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 19:17:58 +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,
|
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
|
// 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
|
// Adding a gauge metric to capture total number of inflight requests
|
||||||
c.metricSink.SetGaugeWithLabels([]string{"core", "in_flight_requests"}, float32(totalInFlightReq), nil)
|
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
|
package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
|
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
@@ -350,3 +354,90 @@ func TestCoreMetrics_EntityGauges(t *testing.T) {
|
|||||||
"mount_point": "auth/userpass/",
|
"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
|
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
|
// DeletePolicy is used to delete the named policy
|
||||||
func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType PolicyType) error {
|
func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType PolicyType) error {
|
||||||
return ps.switchedDeletePolicy(ctx, name, policyType, true, false)
|
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