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:
Vishal Nayak 2017-11-03 11:20:10 -04:00 committed by GitHub
parent 4d3b3bed08
commit d5ad857a86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 191 additions and 45 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

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

View file

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

View file

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