open-vault/helper/identity/templating.go
2018-09-17 23:03:00 -04:00

215 lines
5.1 KiB
Go

package identity
import (
"errors"
"fmt"
"strings"
"github.com/hashicorp/vault/helper/namespace"
)
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
Namespace *namespace.Namespace
}
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(p.Namespace, 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(ns *namespace.Namespace, 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 {
var compare string
if ids {
compare = group.ID
} else {
if ns != nil && group.NamespaceID == ns.ID {
compare = group.Name
} else {
continue
}
}
if compare == accessorSplit[0] {
found = group
break
}
}
if found == nil {
return "", fmt.Errorf("entity is not a member of group %q", accessorSplit[0])
}
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
}