diff --git a/vault/capabilities.go b/vault/capabilities.go index bbde54c8f4..795a150f91 100644 --- a/vault/capabilities.go +++ b/vault/capabilities.go @@ -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 } diff --git a/vault/capabilities_test.go b/vault/capabilities_test.go index 19bd404262..dbe0103fa2 100644 --- a/vault/capabilities_test.go +++ b/vault/capabilities_test.go @@ -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) diff --git a/vault/core.go b/vault/core.go index 3a155f16e2..6ab857dd0c 100644 --- a/vault/core.go +++ b/vault/core.go @@ -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 { diff --git a/website/source/api/system/capabilities-accessor.html.md b/website/source/api/system/capabilities-accessor.html.md index 06a2bf3504..011850ce72 100644 --- a/website/source/api/system/capabilities-accessor.html.md +++ b/website/source/api/system/capabilities-accessor.html.md @@ -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 diff --git a/website/source/api/system/capabilities-self.html.md b/website/source/api/system/capabilities-self.html.md index 4adfb96cd0..6957ea9efd 100644 --- a/website/source/api/system/capabilities-self.html.md +++ b/website/source/api/system/capabilities-self.html.md @@ -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 diff --git a/website/source/api/system/capabilities.html.md b/website/source/api/system/capabilities.html.md index a8ef2c4e5d..c2449b03c0 100644 --- a/website/source/api/system/capabilities.html.md +++ b/website/source/api/system/capabilities.html.md @@ -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