mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 20:36:26 +02:00
parent
3b25846e75
commit
cdc616e098
@ -350,11 +350,17 @@ func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Req
|
||||
if secondEntity != nil {
|
||||
c.logger.Debug("building separate ACL for second entity", "entity_id", secondEntity.ID)
|
||||
secondEntityPolicyNames = make(map[string][]string)
|
||||
_, secondEntityIdentityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, secondEntity.ID, false)
|
||||
secondEntityIdentityPolicies, err := c.fetchCeilingPolicies(ctx, secondEntity)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
allowOnly, err := c.allPoliciesAllowOnly(ctx, secondEntityIdentityPolicies)
|
||||
if err != nil {
|
||||
c.logger.Error("failed to fetch second entity policies", "error", err)
|
||||
return nil, nil, nil, nil, ErrInternalError
|
||||
}
|
||||
if !allowOnly {
|
||||
return nil, nil, nil, nil, logical.ErrPermissionDenied
|
||||
}
|
||||
// Store second entity policies separately - do NOT merge with primary entity's policies
|
||||
for nsID, nsPolicies := range secondEntityIdentityPolicies {
|
||||
secondEntityPolicyNames[nsID] = policyutil.SanitizePolicies(nsPolicies, false)
|
||||
@ -401,7 +407,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Req
|
||||
return nil, nil, nil, nil, ErrInternalError
|
||||
}
|
||||
|
||||
if secondEntity != nil && len(secondEntityPolicyNames) > 0 {
|
||||
if secondEntity != nil {
|
||||
newAcl, err := c.performSecondaryEntityTokenChecks(tokenCtx, acl, secondEntity, secondEntityPolicyNames)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
@ -2980,3 +2986,69 @@ func (c *Core) checkSSCTokenInternal(ctx context.Context, token string, isPerfSt
|
||||
// status code.
|
||||
return "", logical.ErrMissingRequiredState
|
||||
}
|
||||
|
||||
// allPoliciesAllowOnly is a helper function that checks if all policies in
|
||||
// a given set have only "allow" capabilities, and not "deny" or "sudo".
|
||||
//
|
||||
// Example of allow-only policy:
|
||||
//
|
||||
// path "secret/data/team/public/*" {
|
||||
// capabilities = ["read"]
|
||||
// }
|
||||
//
|
||||
// Example of a policy that is not allow-only:
|
||||
//
|
||||
// path "secret/data/team/*" {
|
||||
// capabilities = ["read"]
|
||||
// }
|
||||
//
|
||||
// path "secret/data/team/private/*" {
|
||||
// capabilities = ["deny"]
|
||||
// }
|
||||
func (c *Core) allPoliciesAllowOnly(ctx context.Context, policyNamesByNamespace map[string][]string) (bool, error) {
|
||||
for nsID, policyNames := range policyNamesByNamespace {
|
||||
policyNS, err := NamespaceByID(ctx, nsID, c)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if policyNS == nil {
|
||||
return false, namespace.ErrNoNamespace
|
||||
}
|
||||
|
||||
policyCtx := namespace.ContextWithNamespace(ctx, policyNS)
|
||||
for _, policyName := range policyNames {
|
||||
policy, err := c.policyStore.GetPolicy(policyCtx, policyName, PolicyTypeACL)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if policy == nil {
|
||||
return false, fmt.Errorf("policy %q not found in namespace %q", policyName, policyNS.Path)
|
||||
}
|
||||
if !policyIsAllowOnly(policy) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// policyIsAllowOnly is a helper function that checks if a policy has only "allow" capabilities, and not "deny" or "sudo".
|
||||
func policyIsAllowOnly(policy *Policy) bool {
|
||||
if policy == nil || policy.Name == "root" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, pathRules := range policy.Paths {
|
||||
if pathRules == nil || pathRules.Permissions == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
capabilities := pathRules.Permissions.CapabilitiesBitmap
|
||||
if capabilities&DenyCapabilityInt != 0 || capabilities&SudoCapabilityInt != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -40,3 +40,7 @@ func getEnterpriseTokenAuthorizationDetails(_ map[string]interface{}) []logical.
|
||||
func (c *Core) performSecondaryEntityTokenChecks(_ context.Context, _ *ACL, _ *identity.Entity, _ map[string][]string) (*ACL, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (c *Core) fetchCeilingPolicies(ctx context.Context, entity *identity.Entity) (map[string][]string, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
@ -643,6 +643,99 @@ func TestRequestHandling_fetchACLTokenEntryAndEntity_NilRequest(t *testing.T) {
|
||||
require.Equal(t, ErrInternalError, err)
|
||||
}
|
||||
|
||||
// Test_allPoliciesAllowOnly tests a helper function that checks if all policies in
|
||||
// a given set have only "allow" capabilities, and not "deny" or "sudo"
|
||||
func Test_allPoliciesAllowOnly(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, _, _ := TestCoreUnsealed(t)
|
||||
ctx := namespace.RootContext(context.Background())
|
||||
|
||||
allowPolicy, err := ParseACLPolicy(namespace.RootNamespace, `
|
||||
path "secret/data/*" {
|
||||
capabilities = ["read", "list"]
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
allowPolicy.Name = "allow-only"
|
||||
require.NoError(t, c.policyStore.SetPolicy(ctx, allowPolicy))
|
||||
|
||||
denyPolicy, err := ParseACLPolicy(namespace.RootNamespace, `
|
||||
path "secret/data/*" {
|
||||
capabilities = ["deny"]
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
denyPolicy.Name = "deny-policy"
|
||||
require.NoError(t, c.policyStore.SetPolicy(ctx, denyPolicy))
|
||||
|
||||
sudoPolicy, err := ParseACLPolicy(namespace.RootNamespace, `
|
||||
path "secret/data/*" {
|
||||
capabilities = ["read", "sudo"]
|
||||
}
|
||||
`)
|
||||
require.NoError(t, err)
|
||||
sudoPolicy.Name = "sudo-policy"
|
||||
require.NoError(t, c.policyStore.SetPolicy(ctx, sudoPolicy))
|
||||
|
||||
tests := map[string]struct {
|
||||
policyNamesByNamespace map[string][]string
|
||||
expected bool
|
||||
wantErr string
|
||||
}{
|
||||
"all allow only": {
|
||||
policyNamesByNamespace: map[string][]string{
|
||||
namespace.RootNamespaceID: {"allow-only"},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
"deny policy": {
|
||||
policyNamesByNamespace: map[string][]string{
|
||||
namespace.RootNamespaceID: {"deny-policy"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"sudo policy": {
|
||||
policyNamesByNamespace: map[string][]string{
|
||||
namespace.RootNamespaceID: {"sudo-policy"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"root policy": {
|
||||
policyNamesByNamespace: map[string][]string{
|
||||
namespace.RootNamespaceID: {"root"},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
"missing policy": {
|
||||
policyNamesByNamespace: map[string][]string{
|
||||
namespace.RootNamespaceID: {"missing-policy"},
|
||||
},
|
||||
expected: false,
|
||||
wantErr: "policy \"missing-policy\" not found",
|
||||
},
|
||||
"missing namespace": {
|
||||
policyNamesByNamespace: map[string][]string{
|
||||
"missing-namespace": {"allow-only"},
|
||||
},
|
||||
expected: false,
|
||||
wantErr: namespace.ErrNoNamespace.Error(),
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actual, err := c.allPoliciesAllowOnly(ctx, tc.policyNamesByNamespace)
|
||||
if tc.wantErr != "" {
|
||||
require.ErrorContains(t, err, tc.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Equal(t, tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuth_AuthorizationDetails_CopiedFromRequest verifies that logical.Auth.AuthorizationDetails
|
||||
// matches the authorization details already carried on the request.
|
||||
func TestAuth_AuthorizationDetails_CopiedFromRequest(t *testing.T) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user