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

Support token's metadata in template #10682

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions changelog/10682.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
core: support token metadata in template
```
21 changes: 21 additions & 0 deletions sdk/helper/identitytpl/templating.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

var (
ErrUnbalancedTemplatingCharacter = errors.New("unbalanced templating characters")
ErrNoAttachedToken = errors.New("string contains entity template directives but no token was provided")
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")
Expand All @@ -27,6 +28,7 @@ const (
type PopulateStringInput struct {
String string
ValidityCheckOnly bool
Token *logical.TokenEntry
Entity *logical.Entity
Groups []*logical.Group
NamespaceID string
Expand Down Expand Up @@ -192,6 +194,18 @@ func performTemplating(input string, p *PopulateStringInput) (string, error) {
return "", ErrTemplateValueNotFound
}

performTokenTemplating := func(trimmed string) (string, error) {
switch {
case trimmed == "metadata":
return p.templateHandler(p.Token.Meta)
case strings.HasPrefix(trimmed, "metadata."):
split := strings.SplitN(trimmed, ".", 2)
return p.templateHandler(p.Token.Meta, split[1])
}

return "", ErrTemplateValueNotFound
}

performEntityTemplating := func(trimmed string) (string, error) {
switch {
case trimmed == "id":
Expand Down Expand Up @@ -352,6 +366,13 @@ func performTemplating(input string, p *PopulateStringInput) (string, error) {
}
return performEntityTemplating(strings.TrimPrefix(input, "identity.entity."))

case strings.HasPrefix(input, "token."):
if p.Token == nil {
return "", ErrNoAttachedToken
}

return performTokenTemplating(strings.TrimPrefix(input, "token."))

case strings.HasPrefix(input, "identity.groups."):
if len(p.Groups) == 0 {
return "", ErrNoGroupsAttachedToToken
Expand Down
38 changes: 38 additions & 0 deletions sdk/helper/identitytpl/templating_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,3 +553,41 @@ func TestPopulate_FullObject(t *testing.T) {
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, out)
}
}

func TestPopulate_TokenMetaData(t *testing.T) {
testToken := &logical.TokenEntry{
Meta: map[string]string{
"color": "green",
"size": "small",
"non-printable": "\"\n\t",
},
}

template := `
{
"all metadata": {{token.metadata}},
"one metadata key": {{token.metadata.color}},
"one metadata key not found": {{token.metadata.asldfk}}
}`

expected := `
{
"all metadata": {"color":"green","non-printable":"\"\n\t","size":"small"},
"one metadata key": "green",
"one metadata key not found": ""
}`

input := PopulateStringInput{
Mode: JSONTemplating,
String: template,
Token: testToken,
}
_, out, err := PopulateString(input)
if err != nil {
t.Fatal(err)
}

if out != expected {
t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, out)
}
}
4 changes: 2 additions & 2 deletions vault/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (c *Core) Capabilities(ctx context.Context, token, path string) ([]string,
return nil, &logical.StatusBadRequest{Err: "missing token"}
}

te, err := c.tokenStore.Lookup(ctx, token)
te, err := c.LookupToken(ctx, token)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -76,7 +76,7 @@ func (c *Core) Capabilities(ctx context.Context, token, path string) ([]string,
// Construct the corresponding ACL object. ACL construction should be
// performed on the token's namespace.
tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS)
acl, err := c.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
acl, err := c.policyStore.ACL(tokenCtx, entity, te, policyNames, policies...)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion vault/dynamic_system_view.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (e extendedSystemViewImpl) SudoPrivilege(ctx context.Context, path string,

// Construct the corresponding ACL object. Derive and use a new context that
// uses the req.ClientToken's namespace
acl, err := e.core.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
acl, err := e.core.policyStore.ACL(tokenCtx, entity, te, policyNames, policies...)
if err != nil {
e.core.logger.Error("failed to retrieve ACL for token's policies", "token_policies", te.Policies, "error", err)
return false
Expand Down
10 changes: 6 additions & 4 deletions vault/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/helper/hclutil"
"github.com/hashicorp/vault/sdk/helper/identitytpl"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/copystructure"
)

Expand Down Expand Up @@ -232,14 +233,14 @@ 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, false, nil, 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, performTemplating bool, entity *identity.Entity, groups []*identity.Group, token *logical.TokenEntry) (*Policy, error) {
// Parse the rules
root, err := hcl.Parse(rules)
if err != nil {
Expand Down Expand Up @@ -272,15 +273,15 @@ func parseACLPolicyWithTemplating(ns *namespace.Namespace, rules string, perform
}

if o := list.Filter("path"); len(o.Items) > 0 {
if err := parsePaths(&p, o, performTemplating, entity, groups); err != nil {
if err := parsePaths(&p, o, performTemplating, entity, groups, token); err != nil {
return nil, fmt.Errorf("failed to parse policy: %w", 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, performTemplating bool, entity *identity.Entity, groups []*identity.Group, token *logical.TokenEntry) error {
paths := make([]*PathRules, 0, len(list.Items))
for _, item := range list.Items {
key := "path"
Expand All @@ -293,6 +294,7 @@ func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, en
_, templated, err := identitytpl.PopulateString(identitytpl.PopulateStringInput{
Mode: identitytpl.ACLTemplating,
String: key,
Token: token,
Entity: identity.ToSDKEntity(entity),
Groups: identity.ToSDKGroups(groups),
NamespaceID: result.namespace.ID,
Expand Down
4 changes: 2 additions & 2 deletions vault/policy_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ func (t *TemplateError) Error() string {

// ACL is used to return an ACL which is built using the
// named policies and pre-fetched policies if given.
func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, policyNames map[string][]string, additionalPolicies ...*Policy) (*ACL, error) {
func (ps *PolicyStore) ACL(ctx context.Context, entity *identity.Entity, token *logical.TokenEntry, policyNames map[string][]string, additionalPolicies ...*Policy) (*ACL, error) {
var allPolicies []*Policy

// Fetch the named policies
Expand Down Expand Up @@ -795,7 +795,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, true, entity, groups, token)
if err != nil {
return nil, fmt.Errorf("error parsing templated policy %q: %w", policy.Name, err)
}
Expand Down
2 changes: 1 addition & 1 deletion vault/policy_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func testPolicyStoreACL(t *testing.T, ps *PolicyStore, ns *namespace.Namespace)
}

ctx = namespace.ContextWithNamespace(context.Background(), ns)
acl, err := ps.ACL(ctx, nil, map[string][]string{ns.ID: {"dev", "ops"}})
acl, err := ps.ACL(ctx, nil, nil, map[string][]string{ns.ID: {"dev", "ops"}})
if err != nil {
t.Fatalf("err: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion vault/request_handling.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Req

// Construct the corresponding ACL object. ACL construction should be
// performed on the token's namespace.
acl, err := c.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
acl, err := c.policyStore.ACL(tokenCtx, entity, te, policyNames, policies...)
if err != nil {
if errwrap.ContainsType(err, new(TemplateError)) {
c.logger.Warn("permission denied due to a templated policy being invalid or containing directives not satisfied by the requestor", "error", err)
Expand Down
1 change: 1 addition & 0 deletions website/content/docs/concepts/policies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ injected, and currently the `path` keys in policies allow injection.

| Name | Description |
| :------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- |
| `token.metadata.<metadata key>` | The token's associated metadata |
| `identity.entity.id` | The entity's ID |
| `identity.entity.name` | The entity's name |
| `identity.entity.metadata.<metadata key>` | Metadata associated with the entity for the given key |
Expand Down