Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add identity templating helper to sdk/framework #8088

Merged
merged 4 commits into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions helper/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

proto "github.com/golang/protobuf/proto"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/logical"
)

func (g *Group) Clone() (*Group, error) {
Expand Down Expand Up @@ -63,3 +64,83 @@ func (p *Alias) Clone() (*Alias, error) {

return &clonedAlias, nil
}

// ToSDKAlias converts the provided alias to an SDK compatible alias.
func ToSDKAlias(a *Alias) *logical.Alias {
if a == nil {
return nil
}
metadata := make(map[string]string, len(a.Metadata))
for k, v := range a.Metadata {
metadata[k] = v
}

return &logical.Alias{
Name: a.Name,
ID: a.ID,
MountAccessor: a.MountAccessor,
MountType: a.MountType,
Metadata: metadata,
NamespaceID: a.NamespaceID,
}
}

// ToSDKEntity converts the provided entity to an SDK compatible entity.
func ToSDKEntity(e *Entity) *logical.Entity {
if e == nil {
return nil
}

aliases := make([]*logical.Alias, len(e.Aliases))

for i, a := range e.Aliases {
aliases[i] = ToSDKAlias(a)
}

metadata := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata {
metadata[k] = v
}

return &logical.Entity{
ID: e.ID,
Name: e.Name,
Disabled: e.Disabled,
Aliases: aliases,
Metadata: metadata,
NamespaceID: e.NamespaceID,
}
}

// ToSDKGroup converts the provided group to an SDK compatible group.
func ToSDKGroup(g *Group) *logical.Group {
if g == nil {
return nil
}

metadata := make(map[string]string, len(g.Metadata))
for k, v := range g.Metadata {
metadata[k] = v
}

return &logical.Group{
ID: g.ID,
Name: g.Name,
Metadata: metadata,
NamespaceID: g.NamespaceID,
}
}

// ToSDKGroups converts the provided group list to an SDK compatible group list.
func ToSDKGroups(groups []*Group) []*logical.Group {
if groups == nil {
return nil
}

ret := make([]*logical.Group, len(groups))

for i, g := range groups {
ret[i] = ToSDKGroup(g)
}
return ret
}
57 changes: 57 additions & 0 deletions sdk/framework/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package framework

import (
"errors"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/sdk/helper/identitytpl"
"github.com/hashicorp/vault/sdk/logical"
)

// PopulateIdentityTemplate takes a template string, an entity ID, and an
// instance of system view. It will query system view for information about the
// entity and use the resulting identity information to populate the template
// string.
func PopulateIdentityTemplate(tpl string, entityID string, sysView logical.SystemView) (string, error) {
entity, err := sysView.EntityInfo(entityID)
if err != nil {
return "", err
}
if entity == nil {
return "", errors.New("no entity found")
}

groups, err := sysView.GroupsForEntity(entityID)
if err != nil {
return "", err
}

input := identitytpl.PopulateStringInput{
String: tpl,
Entity: entity,
Groups: groups,
Mode: identitytpl.ACLTemplating,
}

_, out, err := identitytpl.PopulateString(input)
if err != nil {
return "", err
}

return out, nil
}

// ValidateIdentityTemplate takes a template string and returns if the string is
// a valid identity template.
func ValidateIdentityTemplate(tpl string) (bool, error) {
hasTemplating, _, err := identitytpl.PopulateString(identitytpl.PopulateStringInput{
Mode: identitytpl.ACLTemplating,
ValidityCheckOnly: true,
String: tpl,
})
if err != nil {
return false, errwrap.Wrapf("failed to validate policy templating: {{err}}", err)
}

return hasTemplating, nil
}
100 changes: 100 additions & 0 deletions sdk/framework/identity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package framework

import (
"testing"

"github.com/hashicorp/vault/sdk/logical"
)

func TestIdentityTemplating(t *testing.T) {
sysView := &logical.StaticSystemView{
EntityVal: &logical.Entity{
ID: "test-id",
Name: "test",
Aliases: []*logical.Alias{
{
ID: "alias-id",
Name: "test alias",
MountAccessor: "test_mount",
MountType: "secret",
Metadata: map[string]string{
"alias-metadata": "metadata-value",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use a different value than "metadata-value" in the three places it occurs? Would reduce ambiguity in test case expected values.

},
},
},
Metadata: map[string]string{
"entity-metadata": "metadata-value",
},
},
GroupsVal: []*logical.Group{
{
ID: "group1-id",
Name: "group1",
Metadata: map[string]string{
"group-metadata": "metadata-value",
},
},
},
}

tCases := []struct {
tpl string
expected string
}{
{
tpl: "{{identity.entity.id}}",
expected: "test-id",
},
{
tpl: "{{identity.entity.name}}",
expected: "test",
},
{
tpl: "{{identity.entity.metadata.entity-metadata}}",
expected: "metadata-value",
},
{
tpl: "{{identity.entity.aliases.test_mount.id}}",
expected: "alias-id",
},
{
tpl: "{{identity.entity.aliases.test_mount.id}}",
expected: "alias-id",
},
{
tpl: "{{identity.entity.aliases.test_mount.name}}",
expected: "test alias",
},
{
tpl: "{{identity.entity.aliases.test_mount.metadata.alias-metadata}}",
expected: "metadata-value",
},
{
tpl: "{{identity.groups.ids.group1-id.name}}",
expected: "group1",
},
{
tpl: "{{identity.groups.names.group1.id}}",
expected: "group1-id",
},
{
tpl: "{{identity.groups.names.group1.metadata.group-metadata}}",
expected: "metadata-value",
},
{
tpl: "{{identity.groups.ids.group1-id.metadata.group-metadata}}",
expected: "metadata-value",
},
}

for _, tCase := range tCases {
out, err := PopulateIdentityTemplate(tCase.tpl, "test", sysView)
if err != nil {
t.Fatal(err)
}

if out != tCase.expected {
t.Fatalf("got %q, expected %q", out, tCase.expected)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package identity
package identitytpl

import (
"encoding/json"
Expand All @@ -9,7 +9,7 @@ import (
"time"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/logical"
)

var (
Expand All @@ -27,9 +27,9 @@ const (
type PopulateStringInput struct {
String string
ValidityCheckOnly bool
Entity *Entity
Groups []*Group
Namespace *namespace.Namespace
Entity *logical.Entity
Groups []*logical.Group
NamespaceID string
Mode int // processing mode, ACLTemplate or JSONTemplating
Now time.Time // optional, defaults to current time

Expand Down Expand Up @@ -165,7 +165,7 @@ func PopulateString(p PopulateStringInput) (bool, string, error) {

func performTemplating(input string, p *PopulateStringInput) (string, error) {

performAliasTemplating := func(trimmed string, alias *Alias) (string, error) {
performAliasTemplating := func(trimmed string, alias *logical.Alias) (string, error) {
switch {
case trimmed == "id":
return p.templateHandler(alias.ID)
Expand Down Expand Up @@ -210,7 +210,7 @@ func performTemplating(input string, p *PopulateStringInput) (string, error) {
if len(split) != 2 {
return "", errors.New("invalid alias selector")
}
var alias *Alias
var alias *logical.Alias
for _, a := range p.Entity.Aliases {
if split[0] == a.MountAccessor {
alias = a
Expand All @@ -223,7 +223,7 @@ func performTemplating(input string, p *PopulateStringInput) (string, error) {
}

// An empty alias is sufficient for generating defaults
alias = &Alias{Metadata: make(map[string]string)}
alias = &logical.Alias{Metadata: make(map[string]string)}
}
return performAliasTemplating(split[1], alias)
}
Expand Down Expand Up @@ -254,17 +254,16 @@ func performTemplating(input string, p *PopulateStringInput) (string, error) {
if len(accessorSplit) != 2 {
return "", errors.New("invalid groups accessor")
}
var found *Group
var found *logical.Group
for _, group := range p.Groups {
var compare string
if ids {
compare = group.ID
} else {
if p.Namespace != nil && group.NamespaceID == p.Namespace.ID {
compare = group.Name
} else {
if p.NamespaceID != "" && group.NamespaceID != p.NamespaceID {
continue
}
compare = group.Name
}

if compare == accessorSplit[0] {
Expand Down
Loading