[CE] Add workload bind type and templated policy (#19077)

This commit is contained in:
Chris S. Kim 2023-10-05 15:45:41 -04:00 committed by GitHub
parent ca4ff6ba1d
commit ad26494016
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 176 additions and 132 deletions

3
.changelog/19077.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
acl: Adds workload identity templated policy
```

View File

@ -1374,7 +1374,7 @@ func TestACL_HTTP(t *testing.T) {
var list map[string]api.ACLTemplatedPolicyResponse var list map[string]api.ACLTemplatedPolicyResponse
require.NoError(t, json.NewDecoder(resp.Body).Decode(&list)) require.NoError(t, json.NewDecoder(resp.Body).Decode(&list))
require.Len(t, list, 4) require.Len(t, list, 5)
require.Equal(t, api.ACLTemplatedPolicyResponse{ require.Equal(t, api.ACLTemplatedPolicyResponse{
TemplateName: api.ACLTemplatedPolicyServiceName, TemplateName: api.ACLTemplatedPolicyServiceName,

View File

@ -1723,19 +1723,8 @@ func (a *ACL) BindingRuleSet(args *structs.ACLBindingRuleSetRequest, reply *stru
return fmt.Errorf("invalid Binding Rule: BindVars cannot be set when bind type is not templated-policy.") return fmt.Errorf("invalid Binding Rule: BindVars cannot be set when bind type is not templated-policy.")
} }
switch rule.BindType { if err := auth.IsValidBindingRule(rule.BindType, rule.BindName, rule.BindVars, blankID.ProjectedVarNames()); err != nil {
case structs.BindingRuleBindTypeService: return fmt.Errorf("Invalid Binding Rule: invalid BindName or BindVars: %w", err)
case structs.BindingRuleBindTypeNode:
case structs.BindingRuleBindTypeRole:
case structs.BindingRuleBindTypeTemplatedPolicy:
default:
return fmt.Errorf("Invalid Binding Rule: unknown BindType %q", rule.BindType)
}
if valid, err := auth.IsValidBindNameOrBindVars(rule.BindType, rule.BindName, rule.BindVars, blankID.ProjectedVarNames()); err != nil {
return fmt.Errorf("Invalid Binding Rule: invalid BindName or BindVars: %v", err)
} else if !valid {
return fmt.Errorf("Invalid Binding Rule: invalid BindName or BindVars")
} }
req := &structs.ACLBindingRuleBatchSetRequest{ req := &structs.ACLBindingRuleBatchSetRequest{

View File

@ -4,6 +4,7 @@
package auth package auth
import ( import (
"errors"
"fmt" "fmt"
"github.com/hashicorp/go-bexpr" "github.com/hashicorp/go-bexpr"
@ -37,8 +38,8 @@ type BinderStateStore interface {
ACLRoleGetByName(ws memdb.WatchSet, roleName string, entMeta *acl.EnterpriseMeta) (uint64, *structs.ACLRole, error) ACLRoleGetByName(ws memdb.WatchSet, roleName string, entMeta *acl.EnterpriseMeta) (uint64, *structs.ACLRole, error)
} }
// Bindings contains the ACL roles, service identities, node identities and // Bindings contains the ACL roles, service identities, node identities,
// enterprise meta to be assigned to the created token. // templated policies, and enterprise meta to be assigned to the created token.
type Bindings struct { type Bindings struct {
Roles []structs.ACLTokenRoleLink Roles []structs.ACLTokenRoleLink
ServiceIdentities []*structs.ACLServiceIdentity ServiceIdentities []*structs.ACLServiceIdentity
@ -91,30 +92,39 @@ func (b *Binder) Bind(authMethod *structs.ACLAuthMethod, verifiedIdentity *authm
// Compute role, service identity, node identity or templated policy names by interpolating // Compute role, service identity, node identity or templated policy names by interpolating
// the identity's projected variables into the rule BindName templates. // the identity's projected variables into the rule BindName templates.
for _, rule := range matchingRules { for _, rule := range matchingRules {
bindName, templatedPolicy, valid, err := computeBindNameAndVars(rule.BindType, rule.BindName, rule.BindVars, verifiedIdentity.ProjectedVars)
switch {
case err != nil:
return nil, fmt.Errorf("cannot compute %q bind name for bind target: %w", rule.BindType, err)
case !valid:
return nil, fmt.Errorf("computed %q bind name for bind target is invalid: %q", rule.BindType, bindName)
}
switch rule.BindType { switch rule.BindType {
case structs.BindingRuleBindTypeService: case structs.BindingRuleBindTypeService:
bindName, err := computeBindName(rule.BindName, verifiedIdentity.ProjectedVars, acl.IsValidServiceIdentityName)
if err != nil {
return nil, err
}
bindings.ServiceIdentities = append(bindings.ServiceIdentities, &structs.ACLServiceIdentity{ bindings.ServiceIdentities = append(bindings.ServiceIdentities, &structs.ACLServiceIdentity{
ServiceName: bindName, ServiceName: bindName,
}) })
case structs.BindingRuleBindTypeNode: case structs.BindingRuleBindTypeNode:
bindName, err := computeBindName(rule.BindName, verifiedIdentity.ProjectedVars, acl.IsValidNodeIdentityName)
if err != nil {
return nil, err
}
bindings.NodeIdentities = append(bindings.NodeIdentities, &structs.ACLNodeIdentity{ bindings.NodeIdentities = append(bindings.NodeIdentities, &structs.ACLNodeIdentity{
NodeName: bindName, NodeName: bindName,
Datacenter: b.datacenter, Datacenter: b.datacenter,
}) })
case structs.BindingRuleBindTypeTemplatedPolicy: case structs.BindingRuleBindTypeTemplatedPolicy:
templatedPolicy, err := generateTemplatedPolicies(rule.BindName, rule.BindVars, verifiedIdentity.ProjectedVars)
if err != nil {
return nil, err
}
bindings.TemplatedPolicies = append(bindings.TemplatedPolicies, templatedPolicy) bindings.TemplatedPolicies = append(bindings.TemplatedPolicies, templatedPolicy)
case structs.BindingRuleBindTypeRole: case structs.BindingRuleBindTypeRole:
bindName, err := computeBindName(rule.BindName, verifiedIdentity.ProjectedVars, acl.IsValidRoleName)
if err != nil {
return nil, err
}
_, role, err := b.store.ACLRoleGetByName(nil, bindName, &bindings.EnterpriseMeta) _, role, err := b.store.ACLRoleGetByName(nil, bindName, &bindings.EnterpriseMeta)
if err != nil { if err != nil {
return nil, err return nil, err
@ -131,11 +141,11 @@ func (b *Binder) Bind(authMethod *structs.ACLAuthMethod, verifiedIdentity *authm
return &bindings, nil return &bindings, nil
} }
// IsValidBindNameOrBindVars returns whether the given BindName and/or BindVars template produces valid // IsValidBindingRule returns whether the given BindName and/or BindVars template produces valid
// results when interpolating the auth method's available variables. // results when interpolating the auth method's available variables.
func IsValidBindNameOrBindVars(bindType, bindName string, bindVars *structs.ACLTemplatedPolicyVariables, availableVariables []string) (bool, error) { func IsValidBindingRule(bindType, bindName string, bindVars *structs.ACLTemplatedPolicyVariables, availableVariables []string) error {
if bindType == "" || bindName == "" { if bindType == "" || bindName == "" {
return false, nil return errors.New("bindType and bindName must not be empty")
} }
fakeVarMap := make(map[string]string) fakeVarMap := make(map[string]string)
@ -143,63 +153,66 @@ func IsValidBindNameOrBindVars(bindType, bindName string, bindVars *structs.ACLT
fakeVarMap[v] = "fake" fakeVarMap[v] = "fake"
} }
_, _, valid, err := computeBindNameAndVars(bindType, bindName, bindVars, fakeVarMap)
if err != nil {
return false, err
}
return valid, nil
}
// computeBindNameAndVars processes the HIL for the provided bind type+name+vars using the
// projected variables. When bindtype is templated-policy, it returns the resulting templated policy
// otherwise, returns nil
//
// when bindtype is not templated-policy: it evaluates bindName
// - If the HIL is invalid ("", nil, false, AN_ERROR) is returned.
// - If the computed name is not valid for the type ("INVALID_NAME", nil, false, nil) is returned.
// - If the computed name is valid for the type ("VALID_NAME", nil, true, nil) is returned.
// when bindtype is templated-policy: it evalueates both bindName and bindVars
// - If the computed bindvars(failing templated policy schema validation) are invalid ("", nil, false, AN_ERROR) is returned.
// - if the HIL in bindvars is invalid it returns ("", nil, false, AN_ERROR)
// - if the computed bindvars are valid and templated policy validation is successful it returns (bindName, TemplatedPolicy, true, nil)
func computeBindNameAndVars(bindType, bindName string, bindVars *structs.ACLTemplatedPolicyVariables, projectedVars map[string]string) (string, *structs.ACLTemplatedPolicy, bool, error) {
bindName, err := template.InterpolateHIL(bindName, projectedVars, true)
if err != nil {
return "", nil, false, err
}
var templatedPolicy *structs.ACLTemplatedPolicy
var valid bool
switch bindType { switch bindType {
case structs.BindingRuleBindTypeService: case structs.BindingRuleBindTypeService:
valid = acl.IsValidServiceIdentityName(bindName) if _, err := computeBindName(bindName, fakeVarMap, acl.IsValidServiceIdentityName); err != nil {
case structs.BindingRuleBindTypeNode: return fmt.Errorf("failed to validate bindType %q: %w", bindType, err)
valid = acl.IsValidNodeIdentityName(bindName)
case structs.BindingRuleBindTypeRole:
valid = acl.IsValidRoleName(bindName)
case structs.BindingRuleBindTypeTemplatedPolicy:
templatedPolicy, valid, err = generateTemplatedPolicies(bindName, bindVars, projectedVars)
if err != nil {
return "", nil, false, err
} }
case structs.BindingRuleBindTypeNode:
if _, err := computeBindName(bindName, fakeVarMap, acl.IsValidNodeIdentityName); err != nil {
return fmt.Errorf("failed to validate bindType %q: %w", bindType, err)
}
case structs.BindingRuleBindTypeTemplatedPolicy:
// If user-defined templated policies are supported in the future,
// we will need to lookup state to ensure a template exists for given
// bindName. A possible solution is to rip out the check for templated
// policy into its own step which has access to the state store.
if _, err := generateTemplatedPolicies(bindName, bindVars, fakeVarMap); err != nil {
return fmt.Errorf("failed to validate bindType %q: %w", bindType, err)
}
case structs.BindingRuleBindTypeRole:
if _, err := computeBindName(bindName, fakeVarMap, acl.IsValidRoleName); err != nil {
return fmt.Errorf("failed to validate bindType %q: %w", bindType, err)
}
default: default:
return "", nil, false, fmt.Errorf("unknown binding rule bind type: %s", bindType) return fmt.Errorf("Invalid Binding Rule: unknown BindType %q", bindType)
} }
return bindName, templatedPolicy, valid, nil return nil
} }
func generateTemplatedPolicies(bindName string, bindVars *structs.ACLTemplatedPolicyVariables, projectedVars map[string]string) (*structs.ACLTemplatedPolicy, bool, error) { // computeBindName interprets given HIL bindName with any given variables in projectedVars.
computedBindVars, err := computeBindVars(bindVars, projectedVars) // validate (if not nil) will be called on the interpreted string.
func computeBindName(bindName string, projectedVars map[string]string, validate func(string) bool) (string, error) {
computed, err := template.InterpolateHIL(bindName, projectedVars, true)
if err != nil { if err != nil {
return nil, false, err return "", fmt.Errorf("error interpreting template: %w", err)
}
if validate != nil && !validate(computed) {
return "", fmt.Errorf("invalid bind name: %q", computed)
}
return computed, nil
}
// generateTemplatedPolicies fetches a templated policy by bindName then attempts to interpret
// bindVars with any given variables in projectedVars. The resulting template is validated
// by the template's schema.
func generateTemplatedPolicies(
bindName string,
bindVars *structs.ACLTemplatedPolicyVariables,
projectedVars map[string]string,
) (*structs.ACLTemplatedPolicy, error) {
baseTemplate, ok := structs.GetACLTemplatedPolicyBase(bindName)
if !ok {
return nil, fmt.Errorf("Bind name for templated-policy bind type does not match existing template name: %s", bindName)
} }
baseTemplate, ok := structs.GetACLTemplatedPolicyBase(bindName) computedBindVars, err := computeBindVars(bindVars, projectedVars)
if err != nil {
if !ok { return nil, fmt.Errorf("failed to interpret templated policy variables: %w", err)
return nil, false, fmt.Errorf("Bind name for templated-policy bind type does not match existing template name: %s", bindName)
} }
out := &structs.ACLTemplatedPolicy{ out := &structs.ACLTemplatedPolicy{
@ -208,12 +221,11 @@ func generateTemplatedPolicies(bindName string, bindVars *structs.ACLTemplatedPo
TemplateID: baseTemplate.TemplateID, TemplateID: baseTemplate.TemplateID,
} }
err = out.ValidateTemplatedPolicy(baseTemplate.Schema) if err := out.ValidateTemplatedPolicy(baseTemplate.Schema); err != nil {
if err != nil { return nil, fmt.Errorf("templated policy failed validation: %w", err)
return nil, false, fmt.Errorf("templated policy failed validation. Error: %v", err)
} }
return out, true, nil return out, nil
} }
func computeBindVars(bindVars *structs.ACLTemplatedPolicyVariables, projectedVars map[string]string) (*structs.ACLTemplatedPolicyVariables, error) { func computeBindVars(bindVars *structs.ACLTemplatedPolicyVariables, projectedVars map[string]string) (*structs.ACLTemplatedPolicyVariables, error) {

View File

@ -119,7 +119,7 @@ func TestBinder_Roles_NameValidation(t *testing.T) {
_, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{}) _, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{})
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "bind name for bind target is invalid") require.Contains(t, err.Error(), "invalid bind name")
} }
func TestBinder_ServiceIdentities_Success(t *testing.T) { func TestBinder_ServiceIdentities_Success(t *testing.T) {
@ -187,7 +187,7 @@ func TestBinder_ServiceIdentities_NameValidation(t *testing.T) {
_, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{}) _, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{})
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "bind name for bind target is invalid") require.Contains(t, err.Error(), "invalid bind name")
} }
func TestBinder_NodeIdentities_Success(t *testing.T) { func TestBinder_NodeIdentities_Success(t *testing.T) {
@ -255,88 +255,87 @@ func TestBinder_NodeIdentities_NameValidation(t *testing.T) {
_, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{}) _, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{})
require.Error(t, err) require.Error(t, err)
require.Contains(t, err.Error(), "bind name for bind target is invalid") require.Contains(t, err.Error(), "invalid bind name")
} }
func Test_IsValidBindNameOrBindVars(t *testing.T) { func Test_IsValidBindingRule(t *testing.T) {
type testcase struct { type testcase struct {
name string name string
bindType string bindType string
bindName string bindName string
bindVars *structs.ACLTemplatedPolicyVariables bindVars *structs.ACLTemplatedPolicyVariables
fields string fields string
valid bool // valid HIL, invalid contents err bool
err bool // invalid HIL
} }
for _, test := range []testcase{ for _, test := range []testcase{
{"no bind type", {"no bind type",
"", "", nil, "", false, false}, "", "", nil, "", true},
{"bad bind type", {"bad bind type",
"invalid", "blah", nil, "", false, true}, "invalid", "blah", nil, "", true},
// valid HIL, invalid name // valid HIL, invalid name
{"empty", {"empty",
"both", "", nil, "", false, false}, "both", "", nil, "", true},
{"just end", {"just end",
"both", "}", nil, "", false, false}, "both", "}", nil, "", true},
{"var without start", {"var without start",
"both", " item }", nil, "item", false, false}, "both", " item }", nil, "item", true},
{"two vars missing second start", {"two vars missing second start",
"both", "before-${ item }after--more }", nil, "item,more", false, false}, "both", "before-${ item }after--more }", nil, "item,more", true},
// names for the two types are validated differently // names for the two types are validated differently
{"@ is disallowed", {"@ is disallowed",
"both", "bad@name", nil, "", false, false}, "both", "bad@name", nil, "", true},
{"leading dash", {"leading dash",
"role", "-name", nil, "", true, false}, "role", "-name", nil, "", false},
{"leading dash", {"leading dash",
"service", "-name", nil, "", false, false}, "service", "-name", nil, "", true},
{"trailing dash", {"trailing dash",
"role", "name-", nil, "", true, false}, "role", "name-", nil, "", false},
{"trailing dash", {"trailing dash",
"service", "name-", nil, "", false, false}, "service", "name-", nil, "", true},
{"inner dash", {"inner dash",
"both", "name-end", nil, "", true, false}, "both", "name-end", nil, "", false},
{"upper case", {"upper case",
"role", "NAME", nil, "", true, false}, "role", "NAME", nil, "", false},
{"upper case", {"upper case",
"service", "NAME", nil, "", false, false}, "service", "NAME", nil, "", true},
// valid HIL, valid name // valid HIL, valid name
{"no vars", {"no vars",
"both", "nothing", nil, "", true, false}, "both", "nothing", nil, "", false},
{"just var", {"just var",
"both", "${item}", nil, "item", true, false}, "both", "${item}", nil, "item", false},
{"var in middle", {"var in middle",
"both", "before-${item}after", nil, "item", true, false}, "both", "before-${item}after", nil, "item", false},
{"two vars", {"two vars",
"both", "before-${item}after-${more}", nil, "item,more", true, false}, "both", "before-${item}after-${more}", nil, "item,more", false},
// bad // bad
{"no bind name", {"no bind name",
"both", "", nil, "", false, false}, "both", "", nil, "", true},
{"just start", {"just start",
"both", "${", nil, "", false, true}, "both", "${", nil, "", true},
{"backwards", {"backwards",
"both", "}${", nil, "", false, true}, "both", "}${", nil, "", true},
{"no varname", {"no varname",
"both", "${}", nil, "", false, true}, "both", "${}", nil, "", true},
{"missing map key", {"missing map key",
"both", "${item}", nil, "", false, true}, "both", "${item}", nil, "", true},
{"var without end", {"var without end",
"both", "${ item ", nil, "item", false, true}, "both", "${ item ", nil, "item", true},
{"two vars missing first end", {"two vars missing first end",
"both", "before-${ item after-${ more }", nil, "item,more", false, true}, "both", "before-${ item after-${ more }", nil, "item,more", true},
// bind type: templated policy - bad input // bind type: templated policy - bad input
{"templated-policy missing bindvars", "templated-policy", "builtin/service", nil, "", false, true}, {"templated-policy missing bindvars", "templated-policy", "builtin/service", nil, "", true},
{"templated-policy with unknown templated policy name", {"templated-policy with unknown templated policy name",
"templated-policy", "builtin/service", &structs.ACLTemplatedPolicyVariables{Name: "before-${item}after-${more}"}, "", false, true}, "templated-policy", "builtin/service", &structs.ACLTemplatedPolicyVariables{Name: "before-${item}after-${more}"}, "", true},
{"templated-policy with correct bindvars and unknown vars", {"templated-policy with correct bindvars and unknown vars",
"templated-policy", "builtin/fake", &structs.ACLTemplatedPolicyVariables{Name: "test"}, "", false, true}, "templated-policy", "builtin/fake", &structs.ACLTemplatedPolicyVariables{Name: "test"}, "", true},
{"templated-policy with correct bindvars but incorrect HIL", {"templated-policy with correct bindvars but incorrect HIL",
"templated-policy", "builtin/service", &structs.ACLTemplatedPolicyVariables{Name: "before-${ item }after--more }"}, "", false, true}, "templated-policy", "builtin/service", &structs.ACLTemplatedPolicyVariables{Name: "before-${ item }after--more }"}, "", true},
// bind type: templated policy - good input // bind type: templated policy - good input
{"templated-policy with appropriate bindvars", {"templated-policy with appropriate bindvars",
"templated-policy", "builtin/service", &structs.ACLTemplatedPolicyVariables{Name: "before-${item}after-${more}"}, "item,more", true, false}, "templated-policy", "builtin/service", &structs.ACLTemplatedPolicyVariables{Name: "before-${item}after-${more}"}, "item,more", false},
} { } {
var cases []testcase var cases []testcase
if test.bindType == "both" { if test.bindType == "both" {
@ -353,19 +352,13 @@ func Test_IsValidBindNameOrBindVars(t *testing.T) {
test := test test := test
t.Run(test.bindType+"--"+test.name, func(t *testing.T) { t.Run(test.bindType+"--"+test.name, func(t *testing.T) {
t.Parallel() t.Parallel()
valid, err := IsValidBindNameOrBindVars( err := IsValidBindingRule(
test.bindType, test.bindType,
test.bindName, test.bindName,
test.bindVars, test.bindVars,
strings.Split(test.fields, ","), strings.Split(test.fields, ","),
) )
if test.err { require.Equal(t, test.err, err != nil)
require.NotNil(t, err)
require.False(t, valid)
} else {
require.NoError(t, err)
require.Equal(t, test.valid, valid)
}
}) })
} }
} }

View File

@ -11,12 +11,13 @@ import (
"hash/fnv" "hash/fnv"
"html/template" "html/template"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib/stringslice"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/lib/stringslice"
) )
//go:embed acltemplatedpolicy/schemas/node.json //go:embed acltemplatedpolicy/schemas/node.json
@ -25,13 +26,17 @@ var ACLTemplatedPolicyNodeSchema string
//go:embed acltemplatedpolicy/schemas/service.json //go:embed acltemplatedpolicy/schemas/service.json
var ACLTemplatedPolicyServiceSchema string var ACLTemplatedPolicyServiceSchema string
//go:embed acltemplatedpolicy/schemas/workload-identity.json
var ACLTemplatedPolicyWorkloadIdentitySchema string
type ACLTemplatedPolicies []*ACLTemplatedPolicy type ACLTemplatedPolicies []*ACLTemplatedPolicy
const ( const (
ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003" ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003"
ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004" ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004"
ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005" ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005"
ACLTemplatedPolicyNomadServerID = "00000000-0000-0000-0000-000000000006" ACLTemplatedPolicyNomadServerID = "00000000-0000-0000-0000-000000000006"
ACLTemplatedPolicyWorkloadIdentityID = "00000000-0000-0000-0000-000000000007"
ACLTemplatedPolicyNoRequiredVariablesSchema = "" // catch-all schema for all templated policy that don't require a schema ACLTemplatedPolicyNoRequiredVariablesSchema = "" // catch-all schema for all templated policy that don't require a schema
) )
@ -73,6 +78,12 @@ var (
Schema: ACLTemplatedPolicyNoRequiredVariablesSchema, Schema: ACLTemplatedPolicyNoRequiredVariablesSchema,
Template: ACLTemplatedPolicyNomadServer, Template: ACLTemplatedPolicyNomadServer,
}, },
api.ACLTemplatedPolicyWorkloadIdentityName: {
TemplateID: ACLTemplatedPolicyWorkloadIdentityID,
TemplateName: api.ACLTemplatedPolicyWorkloadIdentityName,
Schema: ACLTemplatedPolicyWorkloadIdentitySchema,
Template: ACLTemplatedPolicyWorkloadIdentity,
},
} }
) )

View File

@ -19,6 +19,9 @@ var ACLTemplatedPolicyDNS string
//go:embed acltemplatedpolicy/policies/ce/nomad-server.hcl //go:embed acltemplatedpolicy/policies/ce/nomad-server.hcl
var ACLTemplatedPolicyNomadServer string var ACLTemplatedPolicyNomadServer string
//go:embed acltemplatedpolicy/policies/ce/workload-identity.hcl
var ACLTemplatedPolicyWorkloadIdentity string
func (t *ACLToken) TemplatedPolicyList() []*ACLTemplatedPolicy { func (t *ACLToken) TemplatedPolicyList() []*ACLTemplatedPolicy {
if len(t.TemplatedPolicies) == 0 { if len(t.TemplatedPolicies) == 0 {
return nil return nil

View File

@ -8,8 +8,9 @@ package structs
import ( import (
"testing" "testing"
"github.com/hashicorp/consul/api"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/hashicorp/consul/api"
) )
func TestStructs_ACLTemplatedPolicy_SyntheticPolicy(t *testing.T) { func TestStructs_ACLTemplatedPolicy_SyntheticPolicy(t *testing.T) {
@ -79,6 +80,21 @@ service_prefix "" {
} }
query_prefix "" { query_prefix "" {
policy = "read" policy = "read"
}`,
},
},
"workload-identity-template": {
templatedPolicy: &ACLTemplatedPolicy{
TemplateID: ACLTemplatedPolicyWorkloadIdentityID,
TemplateName: api.ACLTemplatedPolicyWorkloadIdentityName,
TemplateVariables: &ACLTemplatedPolicyVariables{
Name: "api",
},
},
expectedPolicy: &ACLPolicy{
Description: "synthetic policy generated from templated policy: builtin/workload-identity",
Rules: `identity "api" {
policy = "write"
}`, }`,
}, },
}, },

View File

@ -0,0 +1,3 @@
identity "{{.Name}}" {
policy = "write"
}

View File

@ -0,0 +1,13 @@
{
"type": "object",
"properties": {
"name": { "type": "string", "$ref": "#/definitions/min-length-one" }
},
"required": ["name"],
"definitions": {
"min-length-one": {
"type": "string",
"minLength": 1
}
}
}

View File

@ -21,10 +21,11 @@ const (
ACLManagementType = "management" ACLManagementType = "management"
// ACLTemplatedPolicy names // ACLTemplatedPolicy names
ACLTemplatedPolicyServiceName = "builtin/service" ACLTemplatedPolicyServiceName = "builtin/service"
ACLTemplatedPolicyNodeName = "builtin/node" ACLTemplatedPolicyNodeName = "builtin/node"
ACLTemplatedPolicyDNSName = "builtin/dns" ACLTemplatedPolicyDNSName = "builtin/dns"
ACLTemplatedPolicyNomadServerName = "builtin/nomad-server" ACLTemplatedPolicyNomadServerName = "builtin/nomad-server"
ACLTemplatedPolicyWorkloadIdentityName = "builtin/workload-identity"
) )
type ACLLink struct { type ACLLink struct {