mirror of
https://github.com/hashicorp/vault.git
synced 2026-04-01 07:08:05 -04:00
Capabilities responds considering policies on entities and groups (#3522)
* Capabilities endpoint will now return considering policies on entities and groups * refactor the policy derivation into a separate function * Docs: Update docs to reflect the change in capabilities endpoint
This commit is contained in:
parent
4d3b3bed08
commit
d5ad857a86
6 changed files with 191 additions and 45 deletions
|
|
@ -37,6 +37,19 @@ func (c *Core) Capabilities(token, path string) ([]string, error) {
|
|||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
_, derivedPolicies, err := c.fetchEntityAndDerivedPolicies(te.EntityID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range derivedPolicies {
|
||||
policy, err := c.policyStore.GetPolicy(item, PolicyTypeToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
|
||||
if len(policies) == 0 {
|
||||
return []string{DenyCapability}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,118 @@ package vault
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestCapabilities_DerivedPolicies(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
|
||||
i, _, c := testIdentityStoreWithGithubAuth(t)
|
||||
|
||||
policy1 := `
|
||||
name = "policy1"
|
||||
path "secret/sample" {
|
||||
capabilities = ["update", "create", "sudo"]
|
||||
}
|
||||
`
|
||||
policy2 := `
|
||||
name = "policy2"
|
||||
path "secret/sample" {
|
||||
capabilities = ["read", "delete"]
|
||||
}
|
||||
`
|
||||
|
||||
policy3 := `
|
||||
name = "policy3"
|
||||
path "secret/sample" {
|
||||
capabilities = ["list", "list"]
|
||||
}
|
||||
`
|
||||
// Create the above policies
|
||||
policy, _ := ParseACLPolicy(policy1)
|
||||
err = c.policyStore.SetPolicy(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
policy, _ = ParseACLPolicy(policy2)
|
||||
err = c.policyStore.SetPolicy(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
policy, _ = ParseACLPolicy(policy3)
|
||||
err = c.policyStore.SetPolicy(policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
// Create an entity and assign policy1 to it
|
||||
entityReq := &logical.Request{
|
||||
Path: "entity",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"policies": "policy1",
|
||||
},
|
||||
}
|
||||
resp, err = i.HandleRequest(entityReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %#v\n", resp, err)
|
||||
}
|
||||
entityID := resp.Data["id"].(string)
|
||||
|
||||
// Create a token for the entity and assign policy2 on the token
|
||||
ent := &TokenEntry{
|
||||
ID: "capabilitiestoken",
|
||||
Path: "secret/sample",
|
||||
Policies: []string{"policy2"},
|
||||
EntityID: entityID,
|
||||
}
|
||||
if err := c.tokenStore.create(ent); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
actual, err := c.Capabilities("capabilitiestoken", "secret/sample")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
expected := []string{"create", "read", "sudo", "delete", "update"}
|
||||
sort.Strings(actual)
|
||||
sort.Strings(expected)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
|
||||
// Create a group and add the above created entity to it
|
||||
groupReq := &logical.Request{
|
||||
Path: "group",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"member_entity_ids": []string{entityID},
|
||||
"policies": "policy3",
|
||||
},
|
||||
}
|
||||
resp, err = i.HandleRequest(groupReq)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %#v\n", resp, err)
|
||||
}
|
||||
|
||||
actual, err = c.Capabilities("capabilitiestoken", "secret/sample")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
expected = []string{"create", "read", "sudo", "delete", "update", "list"}
|
||||
sort.Strings(actual)
|
||||
sort.Strings(expected)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: got\n%#v\nexpected\n%#v\n", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapabilities(t *testing.T) {
|
||||
c, _, token := TestCoreUnsealed(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -661,6 +661,57 @@ func (c *Core) LookupToken(token string) (*TokenEntry, error) {
|
|||
return c.tokenStore.Lookup(token)
|
||||
}
|
||||
|
||||
// fetchEntityAndDerivedPolicies returns the entity object for the given entity
|
||||
// ID. If the entity is merged into a different entity object, the entity into
|
||||
// which the given entity ID is merged into will be returned. This function
|
||||
// also returns the cumulative list of policies that the entity is entitled to.
|
||||
// This list includes the policies from the entity itself and from all the
|
||||
// groups in which the given entity ID is a member of.
|
||||
func (c *Core) fetchEntityAndDerivedPolicies(entityID string) (*identity.Entity, []string, error) {
|
||||
if entityID == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
//c.logger.Debug("core: entity set on the token", "entity_id", te.EntityID)
|
||||
|
||||
// Fetch the entity
|
||||
entity, err := c.identityStore.MemDBEntityByID(entityID, false)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to lookup entity using its ID", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if entity == nil {
|
||||
// If there was no corresponding entity object found, it is
|
||||
// possible that the entity got merged into another entity. Try
|
||||
// finding entity based on the merged entity index.
|
||||
entity, err = c.identityStore.MemDBEntityByMergedEntityID(entityID, false)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to lookup entity in merged entity ID index", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var policies []string
|
||||
if entity != nil {
|
||||
//c.logger.Debug("core: entity successfully fetched; adding entity policies to token's policies to create ACL")
|
||||
|
||||
// Attach the policies on the entity
|
||||
policies = append(policies, entity.Policies...)
|
||||
|
||||
groupPolicies, err := c.identityStore.groupPoliciesByEntityID(entity.ID)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to fetch group policies", "error", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Attach the policies from all the groups
|
||||
policies = append(policies, groupPolicies...)
|
||||
}
|
||||
|
||||
return entity, policies, err
|
||||
}
|
||||
|
||||
func (c *Core) fetchACLTokenEntryAndEntity(clientToken string) (*ACL, *TokenEntry, *identity.Entity, error) {
|
||||
defer metrics.MeasureSince([]string{"core", "fetch_acl_and_token"}, time.Now())
|
||||
|
||||
|
|
@ -688,47 +739,13 @@ func (c *Core) fetchACLTokenEntryAndEntity(clientToken string) (*ACL, *TokenEntr
|
|||
|
||||
tokenPolicies := te.Policies
|
||||
|
||||
var entity *identity.Entity
|
||||
|
||||
// Append the policies of the entity to those on the tokens and create ACL
|
||||
// off of the combined list.
|
||||
if te.EntityID != "" {
|
||||
//c.logger.Debug("core: entity set on the token", "entity_id", te.EntityID)
|
||||
// Fetch entity for the entity ID in the token entry
|
||||
entity, err = c.identityStore.MemDBEntityByID(te.EntityID, false)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to lookup entity using its ID", "error", err)
|
||||
return nil, nil, nil, ErrInternalError
|
||||
}
|
||||
|
||||
if entity == nil {
|
||||
// If there was no corresponding entity object found, it is
|
||||
// possible that the entity got merged into another entity. Try
|
||||
// finding entity based on the merged entity index.
|
||||
entity, err = c.identityStore.MemDBEntityByMergedEntityID(te.EntityID, false)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to lookup entity in merged entity ID index", "error", err)
|
||||
return nil, nil, nil, ErrInternalError
|
||||
}
|
||||
}
|
||||
|
||||
if entity != nil {
|
||||
//c.logger.Debug("core: entity successfully fetched; adding entity policies to token's policies to create ACL")
|
||||
// Attach the policies on the entity to the policies tied to the token
|
||||
tokenPolicies = append(tokenPolicies, entity.Policies...)
|
||||
|
||||
groupPolicies, err := c.identityStore.groupPoliciesByEntityID(entity.ID)
|
||||
if err != nil {
|
||||
c.logger.Error("core: failed to fetch group policies", "error", err)
|
||||
return nil, nil, nil, ErrInternalError
|
||||
}
|
||||
|
||||
// Attach the policies from all the groups to which this entity ID
|
||||
// belongs to
|
||||
tokenPolicies = append(tokenPolicies, groupPolicies...)
|
||||
}
|
||||
entity, derivedPolicies, err := c.fetchEntityAndDerivedPolicies(te.EntityID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, ErrInternalError
|
||||
}
|
||||
|
||||
tokenPolicies = append(tokenPolicies, derivedPolicies...)
|
||||
|
||||
// Construct the corresponding ACL object
|
||||
acl, err := c.policyStore.ACL(tokenPolicies...)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@ description: |-
|
|||
|
||||
# `/sys/capabilities-accessor`
|
||||
|
||||
The `/sys/capabilities-accessor` endpoint is used to fetch the capabilities of a
|
||||
token associated with an accessor.
|
||||
The `/sys/capabilities-accessor` endpoint is used to fetch the capabilities of
|
||||
a token associated with an accessor. The capabilities returned will be derived
|
||||
from the policies that are on the token, and from the policies to which token
|
||||
is entitled to through the entity and entity's group memberships.
|
||||
|
||||
|
||||
## Query Token Accessor Capabilities
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ description: |-
|
|||
|
||||
# `/sys/capabilities-self`
|
||||
|
||||
The `/sys/capabilities-self` endpoint is used to fetch the capabilities of a the
|
||||
supplied token.
|
||||
The `/sys/capabilities-self` endpoint is used to fetch the capabilities of a
|
||||
the supplied token. The capabilities returned will be derived from the
|
||||
policies that are on the token, and from the policies to which token is
|
||||
entitled to through the entity and entity's group memberships.
|
||||
|
||||
## Query Self Capabilities
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ description: |-
|
|||
|
||||
# `/sys/capabilities`
|
||||
|
||||
The `/sys/capabilities` endpoint is used to fetch the capabilities of a token on
|
||||
a given path.
|
||||
The `/sys/capabilities` endpoint is used to fetch the capabilities of a token
|
||||
on a given path. The capabilities returned will be derived from the policies
|
||||
that are on the token, and from the policies to which token is entitled to
|
||||
through the entity and entity's group memberships.
|
||||
|
||||
## Query Token Capabilities
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue