ACL Templating (#4994)

* Initial work on templating

* Add check for unbalanced closing in front

* Add missing templated assignment

* Add first cut of end-to-end test on templating.

* Make template errors be 403s and finish up testing

* Review feedback
This commit is contained in:
Jeff Mitchell
2018-08-15 14:42:56 -04:00
committed by Brian Kassouf
parent 3f0c33937d
commit 9ccbb91a22
11 changed files with 713 additions and 15 deletions

View File

@@ -0,0 +1,204 @@
package identity
import (
"errors"
"strings"
)
var (
ErrUnbalancedTemplatingCharacter = errors.New("unbalanced templating characters")
ErrNoEntityAttachedToToken = errors.New("string contains entity template directives but no entity was provided")
ErrNoGroupsAttachedToToken = errors.New("string contains groups template directives but no groups were provided")
ErrTemplateValueNotFound = errors.New("no value could be found for one of the template directives")
)
type PopulateStringInput struct {
ValidityCheckOnly bool
String string
Entity *Entity
Groups []*Group
}
func PopulateString(p *PopulateStringInput) (bool, string, error) {
if p == nil {
return false, "", errors.New("nil input")
}
if p.String == "" {
return false, "", nil
}
var subst bool
splitStr := strings.Split(p.String, "{{")
if len(splitStr) >= 1 {
if strings.Index(splitStr[0], "}}") != -1 {
return false, "", ErrUnbalancedTemplatingCharacter
}
if len(splitStr) == 1 {
return false, p.String, nil
}
}
var b strings.Builder
if !p.ValidityCheckOnly {
b.Grow(2 * len(p.String))
}
for i, str := range splitStr {
if i == 0 {
if !p.ValidityCheckOnly {
b.WriteString(str)
}
continue
}
splitPiece := strings.Split(str, "}}")
switch len(splitPiece) {
case 2:
subst = true
if !p.ValidityCheckOnly {
tmplStr, err := performTemplating(strings.TrimSpace(splitPiece[0]), p.Entity, p.Groups)
if err != nil {
return false, "", err
}
b.WriteString(tmplStr)
b.WriteString(splitPiece[1])
}
default:
return false, "", ErrUnbalancedTemplatingCharacter
}
}
return subst, b.String(), nil
}
func performTemplating(input string, entity *Entity, groups []*Group) (string, error) {
performAliasTemplating := func(trimmed string, alias *Alias) (string, error) {
switch {
case trimmed == "id":
return alias.ID, nil
case trimmed == "name":
if alias.Name == "" {
return "", ErrTemplateValueNotFound
}
return alias.Name, nil
case strings.HasPrefix(trimmed, "metadata."):
val, ok := alias.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
if !ok {
return "", ErrTemplateValueNotFound
}
return val, nil
}
return "", ErrTemplateValueNotFound
}
performEntityTemplating := func(trimmed string) (string, error) {
switch {
case trimmed == "id":
return entity.ID, nil
case trimmed == "name":
if entity.Name == "" {
return "", ErrTemplateValueNotFound
}
return entity.Name, nil
case strings.HasPrefix(trimmed, "metadata."):
val, ok := entity.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
if !ok {
return "", ErrTemplateValueNotFound
}
return val, nil
case strings.HasPrefix(trimmed, "aliases."):
split := strings.SplitN(strings.TrimPrefix(trimmed, "aliases."), ".", 2)
if len(split) != 2 {
return "", errors.New("invalid alias selector")
}
var found *Alias
for _, alias := range entity.Aliases {
if split[0] == alias.MountAccessor {
found = alias
break
}
}
if found == nil {
return "", errors.New("alias not found")
}
return performAliasTemplating(split[1], found)
}
return "", ErrTemplateValueNotFound
}
performGroupsTemplating := func(trimmed string) (string, error) {
var ids bool
selectorSplit := strings.SplitN(trimmed, ".", 2)
switch {
case len(selectorSplit) != 2:
return "", errors.New("invalid groups selector")
case selectorSplit[0] == "ids":
ids = true
case selectorSplit[0] == "names":
default:
return "", errors.New("invalid groups selector")
}
trimmed = selectorSplit[1]
accessorSplit := strings.SplitN(trimmed, ".", 2)
if len(accessorSplit) != 2 {
return "", errors.New("invalid groups accessor")
}
var found *Group
for _, group := range groups {
compare := group.Name
if ids {
compare = group.ID
}
if compare == accessorSplit[0] {
found = group
break
}
}
if found == nil {
return "", errors.New("group not found")
}
trimmed = accessorSplit[1]
switch {
case trimmed == "id":
return found.ID, nil
case trimmed == "name":
if found.Name == "" {
return "", ErrTemplateValueNotFound
}
return found.Name, nil
case strings.HasPrefix(trimmed, "metadata."):
val, ok := found.Metadata[strings.TrimPrefix(trimmed, "metadata.")]
if !ok {
return "", ErrTemplateValueNotFound
}
return val, nil
}
return "", ErrTemplateValueNotFound
}
switch {
case strings.HasPrefix(input, "identity.entity."):
if entity == nil {
return "", ErrNoEntityAttachedToToken
}
return performEntityTemplating(strings.TrimPrefix(input, "identity.entity."))
case strings.HasPrefix(input, "identity.groups."):
if len(groups) == 0 {
return "", ErrNoGroupsAttachedToToken
}
return performGroupsTemplating(strings.TrimPrefix(input, "identity.groups."))
}
return "", ErrTemplateValueNotFound
}

View File

@@ -0,0 +1,194 @@
package identity
import (
"errors"
"testing"
)
func TestPopulate_Basic(t *testing.T) {
var tests = []struct {
name string
input string
output string
err error
entityName string
metadata map[string]string
aliasAccessor string
aliasID string
aliasName string
nilEntity bool
validityCheckOnly bool
aliasMetadata map[string]string
groupName string
groupMetadata map[string]string
}{
{
name: "no_templating",
input: "path foobar {",
output: "path foobar {",
},
{
name: "only_closing",
input: "path foobar}} {",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "closing_in_front",
input: "path }} {{foobar}} {",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "closing_in_back",
input: "path {{foobar}} }}",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "basic",
input: "path /{{identity.entity.id}}/ {",
output: "path /entityID/ {",
},
{
name: "multiple",
input: "path {{identity.entity.name}} {\n\tval = {{identity.entity.metadata.foo}}\n}",
entityName: "entityName",
metadata: map[string]string{"foo": "bar"},
output: "path entityName {\n\tval = bar\n}",
},
{
name: "multiple_bad_name",
input: "path {{identity.entity.name}} {\n\tval = {{identity.entity.metadata.foo}}\n}",
metadata: map[string]string{"foo": "bar"},
err: ErrTemplateValueNotFound,
},
{
name: "unbalanced_close",
input: "path {{identity.entity.id}} {\n\tval = {{ent}}ity.metadata.foo}}\n}",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "unbalanced_open",
input: "path {{identity.entity.id}} {\n\tval = {{ent{{ity.metadata.foo}}\n}",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "no_entity_no_directives",
input: "path {{identity.entity.id}} {\n\tval = {{ent{{ity.metadata.foo}}\n}",
err: ErrNoEntityAttachedToToken,
nilEntity: true,
},
{
name: "no_entity_no_diretives",
input: "path name {\n\tval = foo\n}",
output: "path name {\n\tval = foo\n}",
nilEntity: true,
},
{
name: "alias_id_name",
input: "path {{ identity.entity.name}} {\n\tval = {{identity.entity.aliases.foomount.id}}\n}",
entityName: "entityName",
aliasAccessor: "foomount",
aliasID: "aliasID",
metadata: map[string]string{"foo": "bar"},
output: "path entityName {\n\tval = aliasID\n}",
},
{
name: "alias_id_name_bad_selector",
input: "path foobar {\n\tval = {{identity.entity.aliases.foomount}}\n}",
aliasAccessor: "foomount",
err: errors.New("invalid alias selector"),
},
{
name: "alias_id_name_bad_accessor",
input: "path \"foobar\" {\n\tval = {{identity.entity.aliases.barmount.id}}\n}",
aliasAccessor: "foomount",
err: errors.New("alias not found"),
},
{
name: "alias_id_name",
input: "path \"{{identity.entity.name}}\" {\n\tval = {{identity.entity.aliases.foomount.metadata.zip}}\n}",
entityName: "entityName",
aliasAccessor: "foomount",
aliasID: "aliasID",
metadata: map[string]string{"foo": "bar"},
aliasMetadata: map[string]string{"zip": "zap"},
output: "path \"entityName\" {\n\tval = zap\n}",
},
{
name: "group_name",
input: "path \"{{identity.groups.ids.groupID.name}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
output: "path \"groupName\" {\n\tval = entityName\n}",
},
{
name: "group_bad_id",
input: "path \"{{identity.groups.ids.hroupID.name}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
err: errors.New("group not found"),
},
{
name: "group_id",
input: "path \"{{identity.groups.names.groupName.id}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
output: "path \"groupID\" {\n\tval = entityName\n}",
},
{
name: "group_bad_name",
input: "path \"{{identity.groups.names.hroupName.id}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
err: errors.New("group not found"),
},
}
for _, test := range tests {
var entity *Entity
if !test.nilEntity {
entity = &Entity{
ID: "entityID",
Name: test.entityName,
Metadata: test.metadata,
}
}
if test.aliasAccessor != "" {
entity.Aliases = []*Alias{
&Alias{
MountAccessor: test.aliasAccessor,
ID: test.aliasID,
Name: test.aliasName,
Metadata: test.aliasMetadata,
},
}
}
var groups []*Group
if test.groupName != "" {
groups = append(groups, &Group{
ID: "groupID",
Name: test.groupName,
Metadata: test.groupMetadata,
})
}
subst, out, err := PopulateString(&PopulateStringInput{
ValidityCheckOnly: test.validityCheckOnly,
String: test.input,
Entity: entity,
Groups: groups,
})
if err != nil {
if test.err == nil {
t.Fatalf("%s: expected success, got error: %v", test.name, err)
}
if err.Error() != test.err.Error() {
t.Fatalf("%s: got error: %v", test.name, err)
}
}
if out != test.output {
t.Fatalf("%s: bad output: %s", test.name, out)
}
if err == nil && !subst && out != test.input {
t.Fatalf("%s: bad subst flag", test.name)
}
}
}

View File

@@ -1003,6 +1003,10 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr
acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(req)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
c.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
err = logical.ErrPermissionDenied
}
retErr = multierror.Append(retErr, err)
c.stateLock.RUnlock()
return retErr

View File

@@ -43,7 +43,13 @@ func (d dynamicSystemView) SudoPrivilege(ctx context.Context, path string, token
}
// Construct the corresponding ACL object
acl, err := d.core.policyStore.ACL(ctx, te.Policies...)
entity, entityPolicies, err := d.core.fetchEntityAndDerivedPolicies(te.EntityID)
if err != nil {
d.core.logger.Error("failed to fetch entity information", "error", err)
return false
}
acl, err := d.core.policyStore.ACL(ctx, entity, append(entityPolicies, te.Policies...)...)
if err != nil {
d.core.logger.Error("failed to retrieve ACL for token's policies", "token_policies", te.Policies, "error", err)
return false

View File

@@ -172,6 +172,10 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e
acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(req)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
c.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
err = logical.ErrPermissionDenied
}
retErr = multierror.Append(retErr, err)
return retErr
}

View File

@@ -2666,6 +2666,7 @@ func (b *SystemBackend) handlePoliciesSet(policyType PolicyType) framework.Opera
return handleError(err)
}
policy.Paths = p.Paths
policy.Templated = p.Templated
default:
return logical.ErrorResponse("unknown policy type"), nil
@@ -3509,6 +3510,10 @@ func (b *SystemBackend) pathInternalUIMountsRead(ctx context.Context, req *logic
// Load the ACL policies so we can walk the prefix for this mount
acl, te, entity, _, err = b.Core.fetchACLTokenEntryAndEntity(req)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
b.Core.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
err = logical.ErrPermissionDenied
}
return nil, err
}
if entity != nil && entity.Disabled {
@@ -3594,6 +3599,10 @@ func (b *SystemBackend) pathInternalUIMountRead(ctx context.Context, req *logica
// Load the ACL policies so we can walk the prefix for this mount
acl, te, entity, _, err := b.Core.fetchACLTokenEntryAndEntity(req)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
b.Core.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
err = logical.ErrPermissionDenied
}
return nil, err
}
if entity != nil && entity.Disabled {
@@ -3620,6 +3629,10 @@ func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *log
acl, te, entity, _, err := b.Core.fetchACLTokenEntryAndEntity(req)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
b.Core.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
err = logical.ErrPermissionDenied
}
return nil, err
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/vault/helper/hclutil"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/parseutil"
"github.com/mitchellh/copystructure"
)
@@ -81,10 +82,11 @@ var (
// Policy is used to represent the policy specified by
// an ACL configuration.
type Policy struct {
Name string `hcl:"name"`
Paths []*PathRules `hcl:"-"`
Raw string
Type PolicyType
Name string `hcl:"name"`
Paths []*PathRules `hcl:"-"`
Raw string
Type PolicyType
Templated bool
}
// PathRules represents a policy for a path in the namespace.
@@ -152,6 +154,15 @@ func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
// intermediary set of policies, before being compiled into
// the ACL
func ParseACLPolicy(rules string) (*Policy, error) {
// Check for templating
hasTemplating, _, err := identity.PopulateString(&identity.PopulateStringInput{
ValidityCheckOnly: true,
String: rules,
})
if err != nil {
return nil, errwrap.Wrapf("failed to validate policy templating: {{err}}", err)
}
// Parse the rules
root, err := hcl.Parse(rules)
if err != nil {
@@ -177,6 +188,7 @@ func ParseACLPolicy(rules string) (*Policy, error) {
var p Policy
p.Raw = rules
p.Type = PolicyTypeACL
p.Templated = hasTemplating
if err := hcl.DecodeObject(&p, list); err != nil {
return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err)
}

View File

@@ -12,6 +12,7 @@ import (
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/golang-lru"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/identity"
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/logical"
)
@@ -165,9 +166,10 @@ type PolicyStore struct {
// PolicyEntry is used to store a policy by name
type PolicyEntry struct {
Version int
Raw string
Type PolicyType
Version int
Raw string
Templated bool
Type PolicyType
}
// NewPolicyStore creates a new PolicyStore that is backed
@@ -275,9 +277,10 @@ func (ps *PolicyStore) setPolicyInternal(ctx context.Context, p *Policy) error {
defer ps.modifyLock.Unlock()
// Create the entry
entry, err := logical.StorageEntryJSON(p.Name, &PolicyEntry{
Version: 2,
Raw: p.Raw,
Type: p.Type,
Version: 2,
Raw: p.Raw,
Type: p.Type,
Templated: p.Templated,
})
if err != nil {
return errwrap.Wrapf("failed to create entry: {{err}}", err)
@@ -377,6 +380,7 @@ func (ps *PolicyStore) GetPolicy(ctx context.Context, name string, policyType Po
policy.Name = name
policy.Raw = policyEntry.Raw
policy.Type = policyEntry.Type
policy.Templated = policyEntry.Templated
switch policyEntry.Type {
case PolicyTypeACL:
// Parse normally
@@ -471,9 +475,21 @@ func (ps *PolicyStore) DeletePolicy(ctx context.Context, name string, policyType
return nil
}
type TemplateError struct {
Err error
}
func (t *TemplateError) WrappedErrors() []error {
return []error{t.Err}
}
func (t *TemplateError) Error() string {
return t.Err.Error()
}
// ACL is used to return an ACL which is built using the
// named policies.
func (ps *PolicyStore) ACL(ctx context.Context, names ...string) (*ACL, error) {
func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, names ...string) (*ACL, error) {
// Fetch the policies
var policies []*Policy
for _, name := range names {
@@ -481,7 +497,42 @@ func (ps *PolicyStore) ACL(ctx context.Context, names ...string) (*ACL, error) {
if err != nil {
return nil, errwrap.Wrapf("failed to get policy: {{err}}", err)
}
policies = append(policies, p)
if p != nil {
policies = append(policies, p)
}
}
var fetchedGroups bool
var groups []*identity.Group
for i, policy := range policies {
if policy.Type == PolicyTypeACL && policy.Templated {
if !fetchedGroups {
fetchedGroups = true
if entity != nil {
directGroups, inheritedGroups, err := ps.core.identityStore.groupsByEntityID(entity.ID)
if err != nil {
return nil, errwrap.Wrapf("failed to fetch group memberships: {{err}}", err)
}
groups = append(directGroups, inheritedGroups...)
}
}
subst, templated, err := identity.PopulateString(&identity.PopulateStringInput{
String: policy.Raw,
Entity: entity,
Groups: groups,
})
if err != nil {
return nil, &TemplateError{Err: errwrap.Wrapf(fmt.Sprintf("error performing templating on policy %q: {{err}}", policy.Name), err)}
}
if !subst {
ps.logger.Warn("found templated policy that reported no substitutions", "policy", policy.Name)
}
p, err := ParseACLPolicy(templated)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("error parsing templated policy %q: {{err}}", policy.Name), err)
}
policies[i] = p
}
}
// Construct the ACL

View File

@@ -0,0 +1,203 @@
package vault_test
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/vault/api"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"
)
func TestPolicyTemplating(t *testing.T) {
goodPolicy1 := `
path "secret/{{ identity.entity.name}}/*" {
capabilities = ["read", "create", "update"]
}
path "secret/{{ identity.entity.aliases.%s.name}}/*" {
capabilities = ["read", "create", "update"]
}
`
goodPolicy2 := `
path "secret/{{ identity.groups.ids.%s.name}}/*" {
capabilities = ["read", "create", "update"]
}
path "secret/{{ identity.groups.names.%s.id}}/*" {
capabilities = ["read", "create", "update"]
}
`
badPolicy1 := `
path "secret/{{ identity.groups.ids.foobar.name}}/*" {
capabilities = ["read", "create", "update"]
}
`
coreConfig := &vault.CoreConfig{
CredentialBackends: map[string]logical.Factory{
"userpass": credUserpass.Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
core := cluster.Cores[0].Core
vault.TestWaitActive(t, core)
client := cluster.Cores[0].Client
resp, err := client.Logical().Write("identity/entity", map[string]interface{}{
"name": "entity_name",
"policies": []string{
"goodPolicy1",
"badPolicy1",
},
})
if err != nil {
t.Fatal(err)
}
entityID := resp.Data["id"].(string)
resp, err = client.Logical().Write("identity/group", map[string]interface{}{
"policies": []string{
"goodPolicy2",
},
"member_entity_ids": []string{
entityID,
},
"name": "group_name",
})
if err != nil {
t.Fatal(err)
}
groupID := resp.Data["id"]
// Enable userpass auth
err = client.Sys().EnableAuthWithOptions("userpass", &api.EnableAuthOptions{
Type: "userpass",
})
if err != nil {
t.Fatal(err)
}
// Create an external group and renew the token. This should add external
// group policies to the token.
auths, err := client.Sys().ListAuth()
if err != nil {
t.Fatal(err)
}
userpassAccessor := auths["userpass/"].Accessor
// Create an alias
resp, err = client.Logical().Write("identity/entity-alias", map[string]interface{}{
"name": "testuser",
"mount_accessor": userpassAccessor,
"canonical_id": entityID,
})
if err != nil {
t.Fatalf("err:%v resp:%#v", err, resp)
}
// Add a user to userpass backend
_, err = client.Logical().Write("auth/userpass/users/testuser", map[string]interface{}{
"password": "testpassword",
})
if err != nil {
t.Fatal(err)
}
// Write in policies
goodPolicy1 = fmt.Sprintf(goodPolicy1, userpassAccessor)
goodPolicy2 = fmt.Sprintf(goodPolicy2, groupID, "group_name")
err = client.Sys().PutPolicy("goodPolicy1", goodPolicy1)
if err != nil {
t.Fatal(err)
}
err = client.Sys().PutPolicy("goodPolicy2", goodPolicy2)
if err != nil {
t.Fatal(err)
}
// Authenticate
secret, err := client.Logical().Write("auth/userpass/login/testuser", map[string]interface{}{
"password": "testpassword",
})
if err != nil {
t.Fatal(err)
}
clientToken := secret.Auth.ClientToken
var tests = []struct {
name string
path string
fail bool
}{
{
name: "entity name",
path: "secret/entity_name/foo",
},
{
name: "bad entity name",
path: "secret/entityname/foo",
fail: true,
},
{
name: "group name",
path: "secret/group_name/foo",
},
{
name: "group id",
path: fmt.Sprintf("secret/%s/foo", groupID),
},
{
name: "alias name",
path: "secret/testuser/foo",
},
}
rootToken := client.Token()
client.SetToken(clientToken)
for _, test := range tests {
resp, err := client.Logical().Write(test.path, map[string]interface{}{"zip": "zap"})
if err != nil && !test.fail {
if resp.Data["error"].(string) != "permission denied" {
t.Fatalf("unexpected status %v", resp.Data["error"])
}
t.Fatalf("%s: got unexpected error: %v", test.name, err)
}
if err == nil && test.fail {
t.Fatalf("%s: expected error", test.name)
}
}
client.SetToken(rootToken)
err = client.Sys().PutPolicy("badPolicy1", badPolicy1)
if err != nil {
t.Fatal(err)
}
client.SetToken(clientToken)
resp, err = client.Logical().Write("secret/entity_name/foo", map[string]interface{}{"zip": "zap"})
if err == nil {
t.Fatalf("expected error, resp is %#v", *resp)
}
if !strings.Contains(err.Error(), "permission denied") {
t.Fatalf("unexpected status: %v", err)
//if resp.Data["error"].(string) != "permission denied" {
//t.Fatalf("unexpected status %v", resp.Data["error"])
}
}

View File

@@ -196,7 +196,7 @@ func TestPolicyStore_ACL(t *testing.T) {
t.Fatalf("err: %v", err)
}
acl, err := ps.ACL(context.Background(), "dev", "ops")
acl, err := ps.ACL(context.Background(), nil, "dev", "ops")
if err != nil {
t.Fatalf("err: %v", err)
}

View File

@@ -154,8 +154,11 @@ func (c *Core) fetchACLTokenEntryAndEntity(req *logical.Request) (*ACL, *logical
allPolicies := append(te.Policies, identityPolicies...)
// Construct the corresponding ACL object
acl, err := c.policyStore.ACL(c.activeContext, allPolicies...)
acl, err := c.policyStore.ACL(c.activeContext, entity, allPolicies...)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
return nil, nil, nil, nil, err
}
c.logger.Error("failed to construct ACL", "error", err)
return nil, nil, nil, nil, ErrInternalError
}
@@ -181,6 +184,10 @@ func (c *Core) checkToken(ctx context.Context, req *logical.Request, unauth bool
// unauth, we just have no information to attach to the request, so
// ignore errors...this was best-effort anyways
if err != nil && !unauth {
if errwrap.ContainsType(err, new(TemplateError)) {
c.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor")
err = logical.ErrPermissionDenied
}
return nil, te, err
}
}