Skip to content

Commit

Permalink
initial commit to support go templates in policies
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Conraux committed Dec 10, 2018
1 parent a0c684d commit 2dde90b
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 28 deletions.
39 changes: 12 additions & 27 deletions vault/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,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)
Expand All @@ -255,51 +260,31 @@ 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)
}
}

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"
if len(item.Keys) > 0 {
key = item.Keys[0].Token.Value().(string)
}

// Check the path
if performTemplating {
_, templated, err := identity.PopulateString(&identity.PopulateStringInput{
String: key,
Entity: entity,
Groups: groups,
Namespace: result.namespace,
})
if err != nil {
continue
}
key = templated
} else {
hasTemplating, _, err := identity.PopulateString(&identity.PopulateStringInput{
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, "<no_value>") {
continue
}

valid := []string{
Expand Down
2 changes: 1 addition & 1 deletion vault/policy_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,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)
}
Expand Down
115 changes: 115 additions & 0 deletions vault/policy_templating.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 2dde90b

Please # to comment.