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 authored and tionebsalocin committed Jun 22, 2020
1 parent 91fc1c2 commit b44d64d
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 30 deletions.
41 changes: 12 additions & 29 deletions vault/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -257,53 +262,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 := 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, "<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 @@ -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)
}
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 b44d64d

Please # to comment.