open-vault/helper/identity/templating_test.go

495 lines
14 KiB
Go

package identity
import (
"errors"
"fmt"
"math"
"strconv"
"testing"
"time"
"github.com/hashicorp/vault/helper/namespace"
)
// intentionally != time.Now() to catch latent used of time.Now instead of
// passed in values
var testNow = time.Now().Add(100 * time.Hour)
func TestPopulate_Basic(t *testing.T) {
var tests = []struct {
mode int
name string
input string
output string
err error
entityName string
metadata map[string]string
aliasAccessor string
aliasID string
aliasName string
nilEntity bool
validityCheckOnly bool
aliasMetadata map[string]string
groupName string
groupMetadata map[string]string
groupMemberships []string
now time.Time
}{
// time.* tests. Keep tests with time.Now() at the front to avoid false
// positives due to the second changing during the test
{
name: "time now",
input: "{{time.now}}",
output: strconv.Itoa(int(testNow.Unix())),
now: testNow,
},
{
name: "time plus",
input: "{{time.now.plus.1h}}",
output: strconv.Itoa(int(testNow.Unix() + (60 * 60))),
now: testNow,
},
{
name: "time plus",
input: "{{time.now.minus.5m}}",
output: strconv.Itoa(int(testNow.Unix() - (5 * 60))),
now: testNow,
},
{
name: "invalid operator",
input: "{{time.now.divide.5m}}",
err: errors.New("invalid time operator \"divide\""),
},
{
name: "time missing operand",
input: "{{time.now.plus}}",
err: errors.New("missing time operand"),
},
{
name: "no_templating",
input: "path foobar {",
output: "path foobar {",
},
{
name: "only_closing",
input: "path foobar}} {",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "closing_in_front",
input: "path }} {{foobar}} {",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "closing_in_back",
input: "path {{foobar}} }}",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "basic",
input: "path /{{identity.entity.id}}/ {",
output: "path /entityID/ {",
},
{
name: "multiple",
input: "path {{identity.entity.name}} {\n\tval = {{identity.entity.metadata.foo}}\n}",
entityName: "entityName",
metadata: map[string]string{"foo": "bar"},
output: "path entityName {\n\tval = bar\n}",
},
{
name: "multiple_bad_name",
input: "path {{identity.entity.name}} {\n\tval = {{identity.entity.metadata.foo}}\n}",
metadata: map[string]string{"foo": "bar"},
err: ErrTemplateValueNotFound,
},
{
name: "unbalanced_close",
input: "path {{identity.entity.id}} {\n\tval = {{ent}}ity.metadata.foo}}\n}",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "unbalanced_open",
input: "path {{identity.entity.id}} {\n\tval = {{ent{{ity.metadata.foo}}\n}",
err: ErrUnbalancedTemplatingCharacter,
},
{
name: "no_entity_no_directives",
input: "path {{identity.entity.id}} {\n\tval = {{ent{{ity.metadata.foo}}\n}",
err: ErrNoEntityAttachedToToken,
nilEntity: true,
},
{
name: "no_entity_no_diretives",
input: "path name {\n\tval = foo\n}",
output: "path name {\n\tval = foo\n}",
nilEntity: true,
},
{
name: "alias_id_name",
input: "path {{ identity.entity.name}} {\n\tval = {{identity.entity.aliases.foomount.id}} nval = {{identity.entity.aliases.foomount.name}}\n}",
entityName: "entityName",
aliasAccessor: "foomount",
aliasID: "aliasID",
aliasName: "aliasName",
metadata: map[string]string{"foo": "bar"},
output: "path entityName {\n\tval = aliasID nval = aliasName\n}",
},
{
name: "alias_id_name_bad_selector",
input: "path foobar {\n\tval = {{identity.entity.aliases.foomount}}\n}",
aliasAccessor: "foomount",
err: errors.New("invalid alias selector"),
},
{
name: "alias_id_name_bad_accessor",
input: "path \"foobar\" {\n\tval = {{identity.entity.aliases.barmount.id}}\n}",
aliasAccessor: "foomount",
err: errors.New("alias not found"),
},
{
name: "alias_id_name",
input: "path \"{{identity.entity.name}}\" {\n\tval = {{identity.entity.aliases.foomount.metadata.zip}}\n}",
entityName: "entityName",
aliasAccessor: "foomount",
aliasID: "aliasID",
metadata: map[string]string{"foo": "bar"},
aliasMetadata: map[string]string{"zip": "zap"},
output: "path \"entityName\" {\n\tval = zap\n}",
},
{
name: "group_name",
input: "path \"{{identity.groups.ids.groupID.name}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
output: "path \"groupName\" {\n\tval = entityName\n}",
},
{
name: "group_bad_id",
input: "path \"{{identity.groups.ids.hroupID.name}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
err: errors.New("entity is not a member of group \"hroupID\""),
},
{
name: "group_id",
input: "path \"{{identity.groups.names.groupName.id}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
output: "path \"groupID\" {\n\tval = entityName\n}",
},
{
name: "group_bad_name",
input: "path \"{{identity.groups.names.hroupName.id}}\" {\n\tval = {{identity.entity.name}}\n}",
entityName: "entityName",
groupName: "groupName",
err: errors.New("entity is not a member of group \"hroupName\""),
},
{
name: "metadata_object_disallowed",
input: "{{identity.entity.metadata}}",
metadata: map[string]string{"foo": "bar"},
err: ErrTemplateValueNotFound,
},
{
name: "alias_metadata_object_disallowed",
input: "{{identity.entity.aliases.foomount.metadata}}",
aliasAccessor: "foomount",
aliasMetadata: map[string]string{"foo": "bar"},
err: ErrTemplateValueNotFound,
},
{
name: "groups.names_disallowed",
input: "{{identity.entity.groups.names}}",
groupMemberships: []string{"foo", "bar"},
err: ErrTemplateValueNotFound,
},
{
name: "groups.ids_disallowed",
input: "{{identity.entity.groups.ids}}",
groupMemberships: []string{"foo", "bar"},
err: ErrTemplateValueNotFound,
},
// missing selector cases
{
mode: JSONTemplating,
name: "entity id",
input: "{{identity.entity.id}}",
output: `"entityID"`,
},
{
mode: JSONTemplating,
name: "entity name",
input: "{{identity.entity.name}}",
entityName: "entityName",
output: `"entityName"`,
},
{
mode: JSONTemplating,
name: "entity name missing",
input: "{{identity.entity.name}}",
output: `""`,
},
{
mode: JSONTemplating,
name: "alias name/id",
input: "{{identity.entity.aliases.foomount.id}} {{identity.entity.aliases.foomount.name}}",
aliasAccessor: "foomount",
aliasID: "aliasID",
aliasName: "aliasName",
output: `"aliasID" "aliasName"`,
},
{
mode: JSONTemplating,
name: "one metadata key",
input: "{{identity.entity.metadata.color}}",
metadata: map[string]string{"foo": "bar", "color": "green"},
output: `"green"`,
},
{
mode: JSONTemplating,
name: "one metadata key not found",
input: "{{identity.entity.metadata.size}}",
metadata: map[string]string{"foo": "bar", "color": "green"},
output: `""`,
},
{
mode: JSONTemplating,
name: "all entity metadata",
input: "{{identity.entity.metadata}}",
metadata: map[string]string{"foo": "bar", "color": "green"},
output: `{"color":"green","foo":"bar"}`,
},
{
mode: JSONTemplating,
name: "null entity metadata",
input: "{{identity.entity.metadata}}",
output: `{}`,
},
{
mode: JSONTemplating,
name: "groups.names",
input: "{{identity.entity.groups.names}}",
groupMemberships: []string{"foo", "bar"},
output: `["foo","bar"]`,
},
{
mode: JSONTemplating,
name: "groups.ids",
input: "{{identity.entity.groups.ids}}",
groupMemberships: []string{"foo", "bar"},
output: `["foo_0","bar_1"]`,
},
{
mode: JSONTemplating,
name: "one alias metadata key",
input: "{{identity.entity.aliases.aws_123.metadata.color}}",
aliasAccessor: "aws_123",
aliasMetadata: map[string]string{"foo": "bar", "color": "green"},
output: `"green"`,
},
{
mode: JSONTemplating,
name: "one alias metadata key not found",
input: "{{identity.entity.aliases.aws_123.metadata.size}}",
aliasAccessor: "aws_123",
aliasMetadata: map[string]string{"foo": "bar", "color": "green"},
output: `""`,
},
{
mode: JSONTemplating,
name: "one alias metadata, accessor not found",
input: "{{identity.entity.aliases.aws_123.metadata.size}}",
aliasAccessor: "not_gonna_match",
aliasMetadata: map[string]string{"foo": "bar", "color": "green"},
output: `""`,
},
{
mode: JSONTemplating,
name: "all alias metadata",
input: "{{identity.entity.aliases.aws_123.metadata}}",
aliasAccessor: "aws_123",
aliasMetadata: map[string]string{"foo": "bar", "color": "green"},
output: `{"color":"green","foo":"bar"}`,
},
{
mode: JSONTemplating,
name: "null alias metadata",
input: "{{identity.entity.aliases.aws_123.metadata}}",
aliasAccessor: "aws_123",
output: `{}`,
},
{
mode: JSONTemplating,
name: "all alias metadata, accessor not found",
input: "{{identity.entity.aliases.aws_123.metadata}}",
aliasAccessor: "not_gonna_match",
aliasMetadata: map[string]string{"foo": "bar", "color": "green"},
output: `{}`,
},
}
for _, test := range tests {
var entity *Entity
if !test.nilEntity {
entity = &Entity{
ID: "entityID",
Name: test.entityName,
Metadata: test.metadata,
}
}
if test.aliasAccessor != "" {
entity.Aliases = []*Alias{
{
MountAccessor: test.aliasAccessor,
ID: test.aliasID,
Name: test.aliasName,
Metadata: test.aliasMetadata,
},
}
}
var groups []*Group
if test.groupName != "" {
groups = append(groups, &Group{
ID: "groupID",
Name: test.groupName,
Metadata: test.groupMetadata,
NamespaceID: namespace.RootNamespace.ID,
})
}
if test.groupMemberships != nil {
for i, groupName := range test.groupMemberships {
groups = append(groups, &Group{
ID: fmt.Sprintf("%s_%d", groupName, i),
Name: groupName,
})
}
}
subst, out, err := PopulateString(PopulateStringInput{
Mode: test.mode,
ValidityCheckOnly: test.validityCheckOnly,
String: test.input,
Entity: entity,
Groups: groups,
Namespace: namespace.RootNamespace,
Now: test.now,
})
if err != nil {
if test.err == nil {
t.Fatalf("%s: expected success, got error: %v", test.name, err)
}
if err.Error() != test.err.Error() {
t.Fatalf("%s: got error: %v", test.name, err)
}
}
if out != test.output {
t.Fatalf("%s: bad output: %s, expected: %s", test.name, out, test.output)
}
if err == nil && !subst && out != test.input {
t.Fatalf("%s: bad subst flag", test.name)
}
}
}
func TestPopulate_CurrentTime(t *testing.T) {
now := time.Now()
// Test that an unset Now parameter results in current time
input := PopulateStringInput{
Mode: JSONTemplating,
String: `{{time.now}}`,
}
_, out, err := PopulateString(input)
if err != nil {
t.Fatal(err)
}
nowPopulated, err := strconv.Atoi(out)
if err != nil {
t.Fatal(err)
}
diff := math.Abs(float64(int64(nowPopulated) - now.Unix()))
if diff > 1 {
t.Fatalf("expected time within 1 second. Got diff of: %f", diff)
}
}
func TestPopulate_FullObject(t *testing.T) {
var testEntity = &Entity{
ID: "abc-123",
Name: "Entity Name",
Metadata: map[string]string{
"color": "green",
"size": "small",
"non-printable": "\"\n\t",
},
Aliases: []*Alias{
{
MountAccessor: "aws_123",
Metadata: map[string]string{
"service": "ec2",
"region": "west",
},
},
},
}
var testGroups = []*Group{
{ID: "a08b0c02", Name: "g1"},
{ID: "239bef91", Name: "g2"},
}
template := `
{
"id": {{identity.entity.id}},
"name": {{identity.entity.name}},
"all metadata": {{identity.entity.metadata}},
"one metadata key": {{identity.entity.metadata.color}},
"one metadata key not found": {{identity.entity.metadata.asldfk}},
"alias metadata": {{identity.entity.aliases.aws_123.metadata}},
"alias not found metadata": {{identity.entity.aliases.blahblah.metadata}},
"one alias metadata key": {{identity.entity.aliases.aws_123.metadata.service}},
"one not found alias metadata key": {{identity.entity.aliases.blahblah.metadata.service}},
"group names": {{identity.entity.groups.names}},
"group ids": {{identity.entity.groups.ids}},
"repeated and": {"nested element": {{identity.entity.name}}}
}`
expected := `
{
"id": "abc-123",
"name": "Entity Name",
"all metadata": {"color":"green","non-printable":"\"\n\t","size":"small"},
"one metadata key": "green",
"one metadata key not found": "",
"alias metadata": {"region":"west","service":"ec2"},
"alias not found metadata": {},
"one alias metadata key": "ec2",
"one not found alias metadata key": "",
"group names": ["g1","g2"],
"group ids": ["a08b0c02","239bef91"],
"repeated and": {"nested element": "Entity Name"}
}`
input := PopulateStringInput{
Mode: JSONTemplating,
String: template,
Entity: testEntity,
Groups: testGroups,
}
_, out, err := PopulateString(input)
if err != nil {
t.Fatal(err)
}
if out != expected {
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, out)
}
}