diff --git a/vault/policy.go b/vault/policy.go index 0fdb3ea85625..c5c0f4ab82ca 100644 --- a/vault/policy.go +++ b/vault/policy.go @@ -223,15 +223,20 @@ func (p *ACLPermissions) Clone() (*ACLPermissions, error) { // intermediary set of policies, before being compiled into // the ACL func ParseACLPolicy(ns *namespace.Namespace, rules string) (*Policy, error) { - return parseACLPolicyWithTemplating(ns, rules, false, nil, nil) + return parseACLPolicyWithTemplating(ns, rules, nil, nil) } // parseACLPolicyWithTemplating performs the actual work and checks whether we // should perform substitutions. If performTemplating is true we know that it // is templated so we don't check again, otherwise we check to see if it's a // templated policy. -func parseACLPolicyWithTemplating(ns *namespace.Namespace, rules string, performTemplating bool, entity *identity.Entity, groups []*identity.Group) (*Policy, error) { +func parseACLPolicyWithTemplating(ns *namespace.Namespace, rules string, entity *identity.Entity, groups []*identity.Group) (*Policy, error) { // Parse the rules + rules, templated, err := renderPolicy(rules, entity, groups) + if err != nil { + return nil, errwrap.Wrapf("failed to render templated policy: {{err}}", err) + } + root, err := hcl.Parse(rules) if err != nil { return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err) @@ -257,13 +262,14 @@ func parseACLPolicyWithTemplating(ns *namespace.Namespace, rules string, perform Raw: rules, Type: PolicyTypeACL, namespace: ns, + Templated: templated, } if err := hcl.DecodeObject(&p, list); err != nil { return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err) } if o := list.Filter("path"); len(o.Items) > 0 { - if err := parsePaths(&p, o, performTemplating, entity, groups); err != nil { + if err := parsePaths(&p, o, entity, groups); err != nil { return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err) } } @@ -271,7 +277,7 @@ func parseACLPolicyWithTemplating(ns *namespace.Namespace, rules string, perform return &p, nil } -func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, entity *identity.Entity, groups []*identity.Group) error { +func parsePaths(result *Policy, list *ast.ObjectList, entity *identity.Entity, groups []*identity.Group) error { paths := make([]*PathRules, 0, len(list.Items)) for _, item := range list.Items { key := "path" @@ -279,31 +285,8 @@ func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, en key = item.Keys[0].Token.Value().(string) } - // Check the path - if performTemplating { - _, templated, err := identitytpl.PopulateString(identitytpl.PopulateStringInput{ - Mode: identitytpl.ACLTemplating, - String: key, - Entity: identity.ToSDKEntity(entity), - Groups: identity.ToSDKGroups(groups), - NamespaceID: result.namespace.ID, - }) - if err != nil { - continue - } - key = templated - } else { - hasTemplating, _, err := identitytpl.PopulateString(identitytpl.PopulateStringInput{ - Mode: identitytpl.ACLTemplating, - ValidityCheckOnly: true, - String: key, - }) - if err != nil { - return errwrap.Wrapf("failed to validate policy templating: {{err}}", err) - } - if hasTemplating { - result.Templated = true - } + if strings.Contains(key, "") { + continue } valid := []string{ diff --git a/vault/policy_store.go b/vault/policy_store.go index 33f8cf0b6d35..b04fe552dc4b 100644 --- a/vault/policy_store.go +++ b/vault/policy_store.go @@ -787,7 +787,7 @@ func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyN groups = append(directGroups, inheritedGroups...) } } - p, err := parseACLPolicyWithTemplating(policy.namespace, policy.Raw, true, entity, groups) + p, err := parseACLPolicyWithTemplating(policy.namespace, policy.Raw, entity, groups) if err != nil { return nil, errwrap.Wrapf(fmt.Sprintf("error parsing templated policy %q: {{err}}", policy.Name), err) } diff --git a/vault/policy_templating.go b/vault/policy_templating.go new file mode 100644 index 000000000000..08032a6792fa --- /dev/null +++ b/vault/policy_templating.go @@ -0,0 +1,115 @@ +package vault + +import ( + "bytes" + "context" + "fmt" + "strings" + "text/template" + + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/helper/identity" +) + +var templateStaticFuncs = map[string]interface{}{ + "replace": strings.Replace, + "has_prefix": strings.HasPrefix, + "has_suffix": strings.HasSuffix, + "contains": strings.Contains, +} + +var templateFuncFactories = map[string]func(ctx context.Context) interface{}{ + "identity": identityFuncFactory, +} + +type policyTemplateContextKeys int + +const ( + entityContextKey policyTemplateContextKeys = iota + groupsContextKey +) + +func templateFuncs(ctx context.Context) template.FuncMap { + m := make(map[string]interface{}, len(templateFuncFactories)+len(templateStaticFuncs)) + for key, f := range templateStaticFuncs { + m[key] = f + } + for key, f := range templateFuncFactories { + m[key] = f(ctx) + } + return m +} + +func identityFuncFactory(ctx context.Context) interface{} { + entity, ok := ctx.Value(entityContextKey).(*identity.Entity) + if !ok { + entity = nil + } + groups, ok := ctx.Value(groupsContextKey).([]*identity.Group) + if !ok { + groups = nil + } + groupsMap := map[string]map[string]interface{}{} + var entityMap map[string]interface{} + var aliasMap map[string]map[string]interface{} + if entity != nil { + aliasMap = make(map[string]map[string]interface{}, len(entity.Aliases)) + for _, alias := range entity.Aliases { + aliasMap[alias.MountAccessor] = map[string]interface{}{ + "id": alias.ID, + "name": alias.Name, + "metadata": alias.Metadata, + } + } + entityMap = map[string]interface{}{ + "name": entity.Name, + "id": entity.ID, + "metadata": entity.Metadata, + "aliases": aliasMap, + } + } + if groups != nil { + groupsMap["ids"] = make(map[string]interface{}, len(groups)) + groupsMap["names"] = make(map[string]interface{}, len(groups)) + for _, group := range groups { + groupMap := map[string]interface{}{ + "id": group.ID, + "name": group.Name, + "metadata": group.Metadata, + } + groupsMap["ids"][group.ID] = groupMap + groupsMap["names"][group.Name] = groupMap + + } + } + identityMap := map[string]interface{}{ + "entity": entityMap, + "groups": groupsMap, + } + return func() interface{} { return identityMap } +} + +func renderPolicy(rules string, entity *identity.Entity, groups []*identity.Group) (string, bool, error) { + var tpl *template.Template + var err error + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("use of invalid keyword leads to panic, check your policy") + } + }() + + ctx := context.Background() + ctx = context.WithValue(ctx, entityContextKey, entity) + ctx = context.WithValue(ctx, groupsContextKey, groups) + tpl, err = template.New("").Funcs(templateFuncs(ctx)).Parse(rules) + if err != nil { + return "", false, errwrap.Wrapf("failed to template policy: {{err}}", err) + } + buf := &bytes.Buffer{} + if err := tpl.Execute(buf, nil); err != nil { + return "", false, errwrap.Wrapf("failed to execute the template: {{err}}", err) + } + + retStr := buf.String() + return retStr, len(retStr) != len(rules), err +}