Add list of granting policies audit logs (#15457)
* Add list of granting policies audit logs * Add changelog
This commit is contained in:
parent
ec6e362d83
commit
b5472aadf3
|
@ -135,6 +135,20 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config
|
|||
reqEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
if auth.PolicyResults != nil {
|
||||
reqEntry.Auth.PolicyResults = &AuditPolicyResults{
|
||||
Allowed: auth.PolicyResults.Allowed,
|
||||
}
|
||||
|
||||
for _, p := range auth.PolicyResults.GrantingPolicies {
|
||||
reqEntry.Auth.PolicyResults.GrantingPolicies = append(reqEntry.Auth.PolicyResults.GrantingPolicies, PolicyInfo{
|
||||
Name: p.Name,
|
||||
NamespaceId: p.NamespaceId,
|
||||
Type: p.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if req.WrapInfo != nil {
|
||||
reqEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second)
|
||||
}
|
||||
|
@ -277,6 +291,7 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config
|
|||
ID: req.ID,
|
||||
ClientToken: req.ClientToken,
|
||||
ClientTokenAccessor: req.ClientTokenAccessor,
|
||||
ClientID: req.ClientID,
|
||||
Operation: req.Operation,
|
||||
MountType: req.MountType,
|
||||
MountAccessor: req.MountAccessor,
|
||||
|
@ -307,6 +322,20 @@ func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config
|
|||
},
|
||||
}
|
||||
|
||||
if auth.PolicyResults != nil {
|
||||
respEntry.Auth.PolicyResults = &AuditPolicyResults{
|
||||
Allowed: auth.PolicyResults.Allowed,
|
||||
}
|
||||
|
||||
for _, p := range auth.PolicyResults.GrantingPolicies {
|
||||
respEntry.Auth.PolicyResults.GrantingPolicies = append(respEntry.Auth.PolicyResults.GrantingPolicies, PolicyInfo{
|
||||
Name: p.Name,
|
||||
NamespaceId: p.NamespaceId,
|
||||
Type: p.Type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !auth.IssueTime.IsZero() {
|
||||
respEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339)
|
||||
}
|
||||
|
@ -381,6 +410,7 @@ type AuditAuth struct {
|
|||
IdentityPolicies []string `json:"identity_policies,omitempty"`
|
||||
ExternalNamespacePolicies map[string][]string `json:"external_namespace_policies,omitempty"`
|
||||
NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
|
||||
PolicyResults *AuditPolicyResults `json:"policy_results,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
NumUses int `json:"num_uses,omitempty"`
|
||||
RemainingUses int `json:"remaining_uses,omitempty"`
|
||||
|
@ -390,6 +420,17 @@ type AuditAuth struct {
|
|||
TokenIssueTime string `json:"token_issue_time,omitempty"`
|
||||
}
|
||||
|
||||
type AuditPolicyResults struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
GrantingPolicies []PolicyInfo `json:"granting_policies,omitempty"`
|
||||
}
|
||||
|
||||
type PolicyInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
NamespaceId string `json:"namespace_id,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type AuditSecret struct {
|
||||
LeaseID string `json:"lease_id,omitempty"`
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
```release-note:improvement
|
||||
audit: Add a policy_results block into the audit log that contains the set of
|
||||
policies that granted this request access.
|
||||
```
|
|
@ -8,7 +8,8 @@ import (
|
|||
)
|
||||
|
||||
// Auth is the resulting authentication information that is part of
|
||||
// Response for credential backends.
|
||||
// Response for credential backends. It's also attached to Request objects and
|
||||
// defines the authentication used for the request. This value is audit logged.
|
||||
type Auth struct {
|
||||
LeaseOptions
|
||||
|
||||
|
@ -101,6 +102,10 @@ type Auth struct {
|
|||
// Orphan is set if the token does not have a parent
|
||||
Orphan bool `json:"orphan"`
|
||||
|
||||
// PolicyResults is the set of policies that grant the token access to the
|
||||
// requesting path.
|
||||
PolicyResults *PolicyResults `json:"policy_results"`
|
||||
|
||||
// MFARequirement
|
||||
MFARequirement *MFARequirement `json:"mfa_requirement"`
|
||||
}
|
||||
|
@ -108,3 +113,14 @@ type Auth struct {
|
|||
func (a *Auth) GoString() string {
|
||||
return fmt.Sprintf("*%#v", *a)
|
||||
}
|
||||
|
||||
type PolicyResults struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
GrantingPolicies []PolicyInfo `json:"granting_policies"`
|
||||
}
|
||||
|
||||
type PolicyInfo struct {
|
||||
Name string `json:"name"`
|
||||
NamespaceId string `json:"namespace_id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
|
36
vault/acl.go
36
vault/acl.go
|
@ -40,11 +40,12 @@ type PolicyCheckOpts struct {
|
|||
}
|
||||
|
||||
type AuthResults struct {
|
||||
ACLResults *ACLResults
|
||||
Allowed bool
|
||||
RootPrivs bool
|
||||
DeniedError bool
|
||||
Error *multierror.Error
|
||||
ACLResults *ACLResults
|
||||
SentinelResults *SentinelResults
|
||||
Allowed bool
|
||||
RootPrivs bool
|
||||
DeniedError bool
|
||||
Error *multierror.Error
|
||||
}
|
||||
|
||||
type ACLResults struct {
|
||||
|
@ -54,6 +55,11 @@ type ACLResults struct {
|
|||
MFAMethods []string
|
||||
ControlGroup *ControlGroup
|
||||
CapabilitiesBitmap uint32
|
||||
GrantingPolicies []logical.PolicyInfo
|
||||
}
|
||||
|
||||
type SentinelResults struct {
|
||||
GrantingPolicies []logical.PolicyInfo
|
||||
}
|
||||
|
||||
// NewACL is used to construct a policy based ACL from a set of policies.
|
||||
|
@ -126,6 +132,10 @@ func NewACL(ctx context.Context, policies []*Policy) (*ACL, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error cloning ACL permissions: %w", err)
|
||||
}
|
||||
|
||||
// Store this policy name as the policy that permits these
|
||||
// capabilities
|
||||
clonedPerms.GrantingPoliciesMap = addGrantingPoliciesToMap(nil, policy, clonedPerms.CapabilitiesBitmap)
|
||||
switch {
|
||||
case pc.HasSegmentWildcards:
|
||||
a.segmentWildcardPaths[pc.Path] = clonedPerms
|
||||
|
@ -155,6 +165,7 @@ func NewACL(ctx context.Context, policies []*Policy) (*ACL, error) {
|
|||
// Insert the capabilities in this new policy into the existing
|
||||
// value
|
||||
existingPerms.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap
|
||||
existingPerms.GrantingPoliciesMap = addGrantingPoliciesToMap(existingPerms.GrantingPoliciesMap, policy, pc.Permissions.CapabilitiesBitmap)
|
||||
}
|
||||
|
||||
// Note: In these stanzas, we're preferring minimum lifetimes. So
|
||||
|
@ -326,6 +337,11 @@ func (a *ACL) AllowOperation(ctx context.Context, req *logical.Request, capCheck
|
|||
ret.Allowed = true
|
||||
ret.RootPrivs = true
|
||||
ret.IsRoot = true
|
||||
ret.GrantingPolicies = []logical.PolicyInfo{{
|
||||
Name: "root",
|
||||
NamespaceId: "root",
|
||||
Type: "acl",
|
||||
}}
|
||||
return
|
||||
}
|
||||
op := req.Operation
|
||||
|
@ -397,25 +413,33 @@ CHECK:
|
|||
ret.MFAMethods = permissions.MFAMethods
|
||||
ret.ControlGroup = permissions.ControlGroup
|
||||
|
||||
var grantingPolicies []logical.PolicyInfo
|
||||
operationAllowed := false
|
||||
switch op {
|
||||
case logical.ReadOperation:
|
||||
operationAllowed = capabilities&ReadCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[ReadCapabilityInt]
|
||||
case logical.ListOperation:
|
||||
operationAllowed = capabilities&ListCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[ListCapabilityInt]
|
||||
case logical.UpdateOperation:
|
||||
operationAllowed = capabilities&UpdateCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[UpdateCapabilityInt]
|
||||
case logical.DeleteOperation:
|
||||
operationAllowed = capabilities&DeleteCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[DeleteCapabilityInt]
|
||||
case logical.CreateOperation:
|
||||
operationAllowed = capabilities&CreateCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[CreateCapabilityInt]
|
||||
case logical.PatchOperation:
|
||||
operationAllowed = capabilities&PatchCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[PatchCapabilityInt]
|
||||
|
||||
// These three re-use UpdateCapabilityInt since that's the most appropriate
|
||||
// capability/operation mapping
|
||||
case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation:
|
||||
operationAllowed = capabilities&UpdateCapabilityInt > 0
|
||||
grantingPolicies = permissions.GrantingPoliciesMap[UpdateCapabilityInt]
|
||||
|
||||
default:
|
||||
return
|
||||
|
@ -425,6 +449,8 @@ CHECK:
|
|||
return
|
||||
}
|
||||
|
||||
ret.GrantingPolicies = grantingPolicies
|
||||
|
||||
if permissions.MaxWrappingTTL > 0 {
|
||||
if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL {
|
||||
return
|
||||
|
|
|
@ -841,6 +841,127 @@ func TestACL_CreationRace(t *testing.T) {
|
|||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestACLGrantingPolicies(t *testing.T) {
|
||||
ns := namespace.RootNamespace
|
||||
policy, err := ParseACLPolicy(ns, grantingTestPolicy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
merged, err := ParseACLPolicy(ns, grantingTestPolicyMerged)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
ctx := namespace.ContextWithNamespace(context.Background(), ns)
|
||||
|
||||
type tcase struct {
|
||||
path string
|
||||
op logical.Operation
|
||||
policies []*Policy
|
||||
expected []logical.PolicyInfo
|
||||
allowed bool
|
||||
}
|
||||
|
||||
policyInfo := logical.PolicyInfo{
|
||||
Name: "granting_policy",
|
||||
NamespaceId: "root",
|
||||
Type: "acl",
|
||||
}
|
||||
mergedInfo := logical.PolicyInfo{
|
||||
Name: "granting_policy_merged",
|
||||
NamespaceId: "root",
|
||||
Type: "acl",
|
||||
}
|
||||
|
||||
tcases := []tcase{
|
||||
{"kv/foo", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||||
{"kv/foo", logical.UpdateOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||||
{"kv/bad", logical.ReadOperation, []*Policy{policy}, nil, false},
|
||||
{"kv/deny", logical.ReadOperation, []*Policy{policy}, nil, false},
|
||||
{"kv/path/foo", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||||
{"kv/path/longer", logical.ReadOperation, []*Policy{policy}, []logical.PolicyInfo{policyInfo}, true},
|
||||
{"kv/foo", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo, mergedInfo}, true},
|
||||
{"kv/path/longer3", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{mergedInfo}, true},
|
||||
{"kv/bar", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{mergedInfo}, true},
|
||||
{"kv/deny", logical.ReadOperation, []*Policy{policy, merged}, nil, false},
|
||||
{"kv/path/longer", logical.UpdateOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo}, true},
|
||||
{"kv/path/foo", logical.ReadOperation, []*Policy{policy, merged}, []logical.PolicyInfo{policyInfo, mergedInfo}, true},
|
||||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
request := &logical.Request{
|
||||
Path: tc.path,
|
||||
Operation: tc.op,
|
||||
}
|
||||
|
||||
acl, err := NewACL(ctx, tc.policies)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
authResults := acl.AllowOperation(ctx, request, false)
|
||||
if authResults.Allowed != tc.allowed {
|
||||
t.Fatalf("bad: case %#v: %v", tc, authResults.Allowed)
|
||||
}
|
||||
if !reflect.DeepEqual(authResults.GrantingPolicies, tc.expected) {
|
||||
t.Fatalf("bad: case %#v: got\n%#v\nexpected\n%#v\n", tc, authResults.GrantingPolicies, tc.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var grantingTestPolicy = `
|
||||
name = "granting_policy"
|
||||
path "kv/foo" {
|
||||
capabilities = ["update", "read"]
|
||||
}
|
||||
|
||||
path "kv/path/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "kv/path/longer" {
|
||||
capabilities = ["update", "read"]
|
||||
}
|
||||
|
||||
path "kv/path/longer2" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
path "kv/deny" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
|
||||
path "ns1/kv/foo" {
|
||||
capabilities = ["update", "read"]
|
||||
}
|
||||
`
|
||||
|
||||
var grantingTestPolicyMerged = `
|
||||
name = "granting_policy_merged"
|
||||
path "kv/foo" {
|
||||
capabilities = ["update", "read"]
|
||||
}
|
||||
|
||||
path "kv/bar" {
|
||||
capabilities = ["update", "read"]
|
||||
}
|
||||
|
||||
path "kv/path/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "kv/path/longer" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "kv/path/longer3" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "kv/deny" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
`
|
||||
|
||||
var tokenCreationPolicy = `
|
||||
name = "tokenCreation"
|
||||
path "auth/token/create*" {
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/sdk/helper/hclutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/identitytpl"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
|
@ -161,14 +162,15 @@ type IdentityFactor struct {
|
|||
}
|
||||
|
||||
type ACLPermissions struct {
|
||||
CapabilitiesBitmap uint32
|
||||
MinWrappingTTL time.Duration
|
||||
MaxWrappingTTL time.Duration
|
||||
AllowedParameters map[string][]interface{}
|
||||
DeniedParameters map[string][]interface{}
|
||||
RequiredParameters []string
|
||||
MFAMethods []string
|
||||
ControlGroup *ControlGroup
|
||||
CapabilitiesBitmap uint32
|
||||
MinWrappingTTL time.Duration
|
||||
MaxWrappingTTL time.Duration
|
||||
AllowedParameters map[string][]interface{}
|
||||
DeniedParameters map[string][]interface{}
|
||||
RequiredParameters []string
|
||||
MFAMethods []string
|
||||
ControlGroup *ControlGroup
|
||||
GrantingPoliciesMap map[uint32][]logical.PolicyInfo
|
||||
}
|
||||
|
||||
func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
|
||||
|
@ -225,9 +227,43 @@ func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
|
|||
ret.ControlGroup = clonedControlGroup.(*ControlGroup)
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.GrantingPoliciesMap == nil:
|
||||
case len(p.GrantingPoliciesMap) == 0:
|
||||
ret.GrantingPoliciesMap = make(map[uint32][]logical.PolicyInfo)
|
||||
default:
|
||||
clonedGrantingPoliciesMap, err := copystructure.Copy(p.GrantingPoliciesMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.GrantingPoliciesMap = clonedGrantingPoliciesMap.(map[uint32][]logical.PolicyInfo)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func addGrantingPoliciesToMap(m map[uint32][]logical.PolicyInfo, policy *Policy, capabilitiesBitmap uint32) map[uint32][]logical.PolicyInfo {
|
||||
if m == nil {
|
||||
m = make(map[uint32][]logical.PolicyInfo)
|
||||
}
|
||||
|
||||
// For all possible policies, check if the provided capabilities include
|
||||
// them
|
||||
for _, capability := range cap2Int {
|
||||
if capabilitiesBitmap&capability == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
m[capability] = append(m[capability], logical.PolicyInfo{
|
||||
Name: policy.Name,
|
||||
NamespaceId: policy.namespace.ID,
|
||||
Type: "acl",
|
||||
})
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// ParseACLPolicy is used to parse the specified ACL rules into an
|
||||
// intermediary set of policies, before being compiled into
|
||||
// the ACL
|
||||
|
|
|
@ -385,6 +385,10 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
|
|||
RootPrivsRequired: rootPath,
|
||||
})
|
||||
|
||||
auth.PolicyResults = &logical.PolicyResults{
|
||||
Allowed: authResults.Allowed,
|
||||
}
|
||||
|
||||
if !authResults.Allowed {
|
||||
retErr := authResults.Error
|
||||
|
||||
|
@ -410,6 +414,13 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
|
|||
return auth, te, retErr
|
||||
}
|
||||
|
||||
if authResults.ACLResults != nil && len(authResults.ACLResults.GrantingPolicies) > 0 {
|
||||
auth.PolicyResults.GrantingPolicies = authResults.ACLResults.GrantingPolicies
|
||||
}
|
||||
if authResults.SentinelResults != nil && len(authResults.SentinelResults.GrantingPolicies) > 0 {
|
||||
auth.PolicyResults.GrantingPolicies = append(auth.PolicyResults.GrantingPolicies, authResults.SentinelResults.GrantingPolicies...)
|
||||
}
|
||||
|
||||
// If it is an authenticated ( i.e with vault token ) request, increment client count
|
||||
if !unauth && c.activityLog != nil {
|
||||
c.activityLog.HandleTokenUsage(ctx, te, clientID, isTWE)
|
||||
|
|
Loading…
Reference in New Issue