Add list of granting policies audit logs (#15457)

* Add list of granting policies audit logs

* Add changelog
This commit is contained in:
Brian Kassouf 2022-05-16 16:23:08 -07:00 committed by GitHub
parent ec6e362d83
commit b5472aadf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 269 additions and 14 deletions

View File

@ -135,6 +135,20 @@ func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config
reqEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339) 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 { if req.WrapInfo != nil {
reqEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second) 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, ID: req.ID,
ClientToken: req.ClientToken, ClientToken: req.ClientToken,
ClientTokenAccessor: req.ClientTokenAccessor, ClientTokenAccessor: req.ClientTokenAccessor,
ClientID: req.ClientID,
Operation: req.Operation, Operation: req.Operation,
MountType: req.MountType, MountType: req.MountType,
MountAccessor: req.MountAccessor, 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() { if !auth.IssueTime.IsZero() {
respEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339) respEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339)
} }
@ -381,6 +410,7 @@ type AuditAuth struct {
IdentityPolicies []string `json:"identity_policies,omitempty"` IdentityPolicies []string `json:"identity_policies,omitempty"`
ExternalNamespacePolicies map[string][]string `json:"external_namespace_policies,omitempty"` ExternalNamespacePolicies map[string][]string `json:"external_namespace_policies,omitempty"`
NoDefaultPolicy bool `json:"no_default_policy,omitempty"` NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
PolicyResults *AuditPolicyResults `json:"policy_results,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"` Metadata map[string]string `json:"metadata,omitempty"`
NumUses int `json:"num_uses,omitempty"` NumUses int `json:"num_uses,omitempty"`
RemainingUses int `json:"remaining_uses,omitempty"` RemainingUses int `json:"remaining_uses,omitempty"`
@ -390,6 +420,17 @@ type AuditAuth struct {
TokenIssueTime string `json:"token_issue_time,omitempty"` 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 { type AuditSecret struct {
LeaseID string `json:"lease_id,omitempty"` LeaseID string `json:"lease_id,omitempty"`
} }

4
changelog/15457.txt Normal file
View File

@ -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.
```

View File

@ -8,7 +8,8 @@ import (
) )
// Auth is the resulting authentication information that is part of // 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 { type Auth struct {
LeaseOptions LeaseOptions
@ -101,6 +102,10 @@ type Auth struct {
// Orphan is set if the token does not have a parent // Orphan is set if the token does not have a parent
Orphan bool `json:"orphan"` 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 *MFARequirement `json:"mfa_requirement"` MFARequirement *MFARequirement `json:"mfa_requirement"`
} }
@ -108,3 +113,14 @@ type Auth struct {
func (a *Auth) GoString() string { func (a *Auth) GoString() string {
return fmt.Sprintf("*%#v", *a) 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"`
}

View File

@ -40,11 +40,12 @@ type PolicyCheckOpts struct {
} }
type AuthResults struct { type AuthResults struct {
ACLResults *ACLResults ACLResults *ACLResults
Allowed bool SentinelResults *SentinelResults
RootPrivs bool Allowed bool
DeniedError bool RootPrivs bool
Error *multierror.Error DeniedError bool
Error *multierror.Error
} }
type ACLResults struct { type ACLResults struct {
@ -54,6 +55,11 @@ type ACLResults struct {
MFAMethods []string MFAMethods []string
ControlGroup *ControlGroup ControlGroup *ControlGroup
CapabilitiesBitmap uint32 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. // 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 { if err != nil {
return nil, fmt.Errorf("error cloning ACL permissions: %w", err) 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 { switch {
case pc.HasSegmentWildcards: case pc.HasSegmentWildcards:
a.segmentWildcardPaths[pc.Path] = clonedPerms 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 // Insert the capabilities in this new policy into the existing
// value // value
existingPerms.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap 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 // 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.Allowed = true
ret.RootPrivs = true ret.RootPrivs = true
ret.IsRoot = true ret.IsRoot = true
ret.GrantingPolicies = []logical.PolicyInfo{{
Name: "root",
NamespaceId: "root",
Type: "acl",
}}
return return
} }
op := req.Operation op := req.Operation
@ -397,25 +413,33 @@ CHECK:
ret.MFAMethods = permissions.MFAMethods ret.MFAMethods = permissions.MFAMethods
ret.ControlGroup = permissions.ControlGroup ret.ControlGroup = permissions.ControlGroup
var grantingPolicies []logical.PolicyInfo
operationAllowed := false operationAllowed := false
switch op { switch op {
case logical.ReadOperation: case logical.ReadOperation:
operationAllowed = capabilities&ReadCapabilityInt > 0 operationAllowed = capabilities&ReadCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[ReadCapabilityInt]
case logical.ListOperation: case logical.ListOperation:
operationAllowed = capabilities&ListCapabilityInt > 0 operationAllowed = capabilities&ListCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[ListCapabilityInt]
case logical.UpdateOperation: case logical.UpdateOperation:
operationAllowed = capabilities&UpdateCapabilityInt > 0 operationAllowed = capabilities&UpdateCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[UpdateCapabilityInt]
case logical.DeleteOperation: case logical.DeleteOperation:
operationAllowed = capabilities&DeleteCapabilityInt > 0 operationAllowed = capabilities&DeleteCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[DeleteCapabilityInt]
case logical.CreateOperation: case logical.CreateOperation:
operationAllowed = capabilities&CreateCapabilityInt > 0 operationAllowed = capabilities&CreateCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[CreateCapabilityInt]
case logical.PatchOperation: case logical.PatchOperation:
operationAllowed = capabilities&PatchCapabilityInt > 0 operationAllowed = capabilities&PatchCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[PatchCapabilityInt]
// These three re-use UpdateCapabilityInt since that's the most appropriate // These three re-use UpdateCapabilityInt since that's the most appropriate
// capability/operation mapping // capability/operation mapping
case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation: case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation:
operationAllowed = capabilities&UpdateCapabilityInt > 0 operationAllowed = capabilities&UpdateCapabilityInt > 0
grantingPolicies = permissions.GrantingPoliciesMap[UpdateCapabilityInt]
default: default:
return return
@ -425,6 +449,8 @@ CHECK:
return return
} }
ret.GrantingPolicies = grantingPolicies
if permissions.MaxWrappingTTL > 0 { if permissions.MaxWrappingTTL > 0 {
if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL { if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL {
return return

View File

@ -841,6 +841,127 @@ func TestACL_CreationRace(t *testing.T) {
wg.Wait() 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 = ` var tokenCreationPolicy = `
name = "tokenCreation" name = "tokenCreation"
path "auth/token/create*" { path "auth/token/create*" {

View File

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/hclutil" "github.com/hashicorp/vault/sdk/helper/hclutil"
"github.com/hashicorp/vault/sdk/helper/identitytpl" "github.com/hashicorp/vault/sdk/helper/identitytpl"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/copystructure" "github.com/mitchellh/copystructure"
) )
@ -161,14 +162,15 @@ type IdentityFactor struct {
} }
type ACLPermissions struct { type ACLPermissions struct {
CapabilitiesBitmap uint32 CapabilitiesBitmap uint32
MinWrappingTTL time.Duration MinWrappingTTL time.Duration
MaxWrappingTTL time.Duration MaxWrappingTTL time.Duration
AllowedParameters map[string][]interface{} AllowedParameters map[string][]interface{}
DeniedParameters map[string][]interface{} DeniedParameters map[string][]interface{}
RequiredParameters []string RequiredParameters []string
MFAMethods []string MFAMethods []string
ControlGroup *ControlGroup ControlGroup *ControlGroup
GrantingPoliciesMap map[uint32][]logical.PolicyInfo
} }
func (p *ACLPermissions) Clone() (*ACLPermissions, error) { func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
@ -225,9 +227,43 @@ func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
ret.ControlGroup = clonedControlGroup.(*ControlGroup) 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 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 // ParseACLPolicy is used to parse the specified ACL rules into an
// intermediary set of policies, before being compiled into // intermediary set of policies, before being compiled into
// the ACL // the ACL

View File

@ -385,6 +385,10 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
RootPrivsRequired: rootPath, RootPrivsRequired: rootPath,
}) })
auth.PolicyResults = &logical.PolicyResults{
Allowed: authResults.Allowed,
}
if !authResults.Allowed { if !authResults.Allowed {
retErr := authResults.Error retErr := authResults.Error
@ -410,6 +414,13 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
return auth, te, retErr 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 it is an authenticated ( i.e with vault token ) request, increment client count
if !unauth && c.activityLog != nil { if !unauth && c.activityLog != nil {
c.activityLog.HandleTokenUsage(ctx, te, clientID, isTWE) c.activityLog.HandleTokenUsage(ctx, te, clientID, isTWE)