diff --git a/.changelog/19077.txt b/.changelog/19077.txt new file mode 100644 index 0000000000..d7c9b0483c --- /dev/null +++ b/.changelog/19077.txt @@ -0,0 +1,3 @@ +```release-note:feature +acl: Adds workload identity templated policy +``` diff --git a/agent/acl_endpoint_test.go b/agent/acl_endpoint_test.go index 942eaec856..7ce090204d 100644 --- a/agent/acl_endpoint_test.go +++ b/agent/acl_endpoint_test.go @@ -1374,7 +1374,7 @@ func TestACL_HTTP(t *testing.T) { var list map[string]api.ACLTemplatedPolicyResponse require.NoError(t, json.NewDecoder(resp.Body).Decode(&list)) - require.Len(t, list, 4) + require.Len(t, list, 5) require.Equal(t, api.ACLTemplatedPolicyResponse{ TemplateName: api.ACLTemplatedPolicyServiceName, diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 300d1097b8..2b8eb88952 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -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.") } - switch rule.BindType { - case structs.BindingRuleBindTypeService: - 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") + if err := auth.IsValidBindingRule(rule.BindType, rule.BindName, rule.BindVars, blankID.ProjectedVarNames()); err != nil { + return fmt.Errorf("Invalid Binding Rule: invalid BindName or BindVars: %w", err) } req := &structs.ACLBindingRuleBatchSetRequest{ diff --git a/agent/consul/auth/binder.go b/agent/consul/auth/binder.go index 3cc56aadd6..1964cc5a87 100644 --- a/agent/consul/auth/binder.go +++ b/agent/consul/auth/binder.go @@ -4,6 +4,7 @@ package auth import ( + "errors" "fmt" "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) } -// Bindings contains the ACL roles, service identities, node identities and -// enterprise meta to be assigned to the created token. +// Bindings contains the ACL roles, service identities, node identities, +// templated policies, and enterprise meta to be assigned to the created token. type Bindings struct { Roles []structs.ACLTokenRoleLink 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 // the identity's projected variables into the rule BindName templates. 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 { 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{ ServiceName: bindName, }) 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{ NodeName: bindName, Datacenter: b.datacenter, }) case structs.BindingRuleBindTypeTemplatedPolicy: + templatedPolicy, err := generateTemplatedPolicies(rule.BindName, rule.BindVars, verifiedIdentity.ProjectedVars) + if err != nil { + return nil, err + } bindings.TemplatedPolicies = append(bindings.TemplatedPolicies, templatedPolicy) 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) if err != nil { return nil, err @@ -131,11 +141,11 @@ func (b *Binder) Bind(authMethod *structs.ACLAuthMethod, verifiedIdentity *authm 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. -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 == "" { - return false, nil + return errors.New("bindType and bindName must not be empty") } fakeVarMap := make(map[string]string) @@ -143,63 +153,66 @@ func IsValidBindNameOrBindVars(bindType, bindName string, bindVars *structs.ACLT 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 { case structs.BindingRuleBindTypeService: - valid = acl.IsValidServiceIdentityName(bindName) - case structs.BindingRuleBindTypeNode: - 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 + if _, err := computeBindName(bindName, fakeVarMap, acl.IsValidServiceIdentityName); err != nil { + return fmt.Errorf("failed to validate bindType %q: %w", bindType, 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: - 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) { - computedBindVars, err := computeBindVars(bindVars, projectedVars) +// computeBindName interprets given HIL bindName with any given variables in 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 { - 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) - - if !ok { - return nil, false, fmt.Errorf("Bind name for templated-policy bind type does not match existing template name: %s", bindName) + computedBindVars, err := computeBindVars(bindVars, projectedVars) + if err != nil { + return nil, fmt.Errorf("failed to interpret templated policy variables: %w", err) } out := &structs.ACLTemplatedPolicy{ @@ -208,12 +221,11 @@ func generateTemplatedPolicies(bindName string, bindVars *structs.ACLTemplatedPo TemplateID: baseTemplate.TemplateID, } - err = out.ValidateTemplatedPolicy(baseTemplate.Schema) - if err != nil { - return nil, false, fmt.Errorf("templated policy failed validation. Error: %v", err) + if err := out.ValidateTemplatedPolicy(baseTemplate.Schema); err != nil { + return nil, fmt.Errorf("templated policy failed validation: %w", err) } - return out, true, nil + return out, nil } func computeBindVars(bindVars *structs.ACLTemplatedPolicyVariables, projectedVars map[string]string) (*structs.ACLTemplatedPolicyVariables, error) { diff --git a/agent/consul/auth/binder_test.go b/agent/consul/auth/binder_test.go index a4b55d1c54..41e0d14ddc 100644 --- a/agent/consul/auth/binder_test.go +++ b/agent/consul/auth/binder_test.go @@ -119,7 +119,7 @@ func TestBinder_Roles_NameValidation(t *testing.T) { _, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{}) 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) { @@ -187,7 +187,7 @@ func TestBinder_ServiceIdentities_NameValidation(t *testing.T) { _, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{}) 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) { @@ -255,88 +255,87 @@ func TestBinder_NodeIdentities_NameValidation(t *testing.T) { _, err := binder.Bind(&structs.ACLAuthMethod{}, &authmethod.Identity{}) 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 { name string bindType string bindName string bindVars *structs.ACLTemplatedPolicyVariables fields string - valid bool // valid HIL, invalid contents - err bool // invalid HIL + err bool } for _, test := range []testcase{ {"no bind type", - "", "", nil, "", false, false}, + "", "", nil, "", true}, {"bad bind type", - "invalid", "blah", nil, "", false, true}, + "invalid", "blah", nil, "", true}, // valid HIL, invalid name {"empty", - "both", "", nil, "", false, false}, + "both", "", nil, "", true}, {"just end", - "both", "}", nil, "", false, false}, + "both", "}", nil, "", true}, {"var without start", - "both", " item }", nil, "item", false, false}, + "both", " item }", nil, "item", true}, {"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 {"@ is disallowed", - "both", "bad@name", nil, "", false, false}, + "both", "bad@name", nil, "", true}, {"leading dash", - "role", "-name", nil, "", true, false}, + "role", "-name", nil, "", false}, {"leading dash", - "service", "-name", nil, "", false, false}, + "service", "-name", nil, "", true}, {"trailing dash", - "role", "name-", nil, "", true, false}, + "role", "name-", nil, "", false}, {"trailing dash", - "service", "name-", nil, "", false, false}, + "service", "name-", nil, "", true}, {"inner dash", - "both", "name-end", nil, "", true, false}, + "both", "name-end", nil, "", false}, {"upper case", - "role", "NAME", nil, "", true, false}, + "role", "NAME", nil, "", false}, {"upper case", - "service", "NAME", nil, "", false, false}, + "service", "NAME", nil, "", true}, // valid HIL, valid name {"no vars", - "both", "nothing", nil, "", true, false}, + "both", "nothing", nil, "", false}, {"just var", - "both", "${item}", nil, "item", true, false}, + "both", "${item}", nil, "item", false}, {"var in middle", - "both", "before-${item}after", nil, "item", true, false}, + "both", "before-${item}after", nil, "item", false}, {"two vars", - "both", "before-${item}after-${more}", nil, "item,more", true, false}, + "both", "before-${item}after-${more}", nil, "item,more", false}, // bad {"no bind name", - "both", "", nil, "", false, false}, + "both", "", nil, "", true}, {"just start", - "both", "${", nil, "", false, true}, + "both", "${", nil, "", true}, {"backwards", - "both", "}${", nil, "", false, true}, + "both", "}${", nil, "", true}, {"no varname", - "both", "${}", nil, "", false, true}, + "both", "${}", nil, "", true}, {"missing map key", - "both", "${item}", nil, "", false, true}, + "both", "${item}", nil, "", true}, {"var without end", - "both", "${ item ", nil, "item", false, true}, + "both", "${ item ", nil, "item", true}, {"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 - {"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", "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", "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", "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 {"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 if test.bindType == "both" { @@ -353,19 +352,13 @@ func Test_IsValidBindNameOrBindVars(t *testing.T) { test := test t.Run(test.bindType+"--"+test.name, func(t *testing.T) { t.Parallel() - valid, err := IsValidBindNameOrBindVars( + err := IsValidBindingRule( test.bindType, test.bindName, test.bindVars, strings.Split(test.fields, ","), ) - if test.err { - require.NotNil(t, err) - require.False(t, valid) - } else { - require.NoError(t, err) - require.Equal(t, test.valid, valid) - } + require.Equal(t, test.err, err != nil) }) } } diff --git a/agent/structs/acl_templated_policy.go b/agent/structs/acl_templated_policy.go index eeb1537988..d0da96e4eb 100644 --- a/agent/structs/acl_templated_policy.go +++ b/agent/structs/acl_templated_policy.go @@ -11,12 +11,13 @@ import ( "hash/fnv" "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/xeipuuv/gojsonschema" "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 @@ -25,13 +26,17 @@ var ACLTemplatedPolicyNodeSchema string //go:embed acltemplatedpolicy/schemas/service.json var ACLTemplatedPolicyServiceSchema string +//go:embed acltemplatedpolicy/schemas/workload-identity.json +var ACLTemplatedPolicyWorkloadIdentitySchema string + type ACLTemplatedPolicies []*ACLTemplatedPolicy const ( - ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003" - ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004" - ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005" - ACLTemplatedPolicyNomadServerID = "00000000-0000-0000-0000-000000000006" + ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003" + ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004" + ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005" + 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 ) @@ -73,6 +78,12 @@ var ( Schema: ACLTemplatedPolicyNoRequiredVariablesSchema, Template: ACLTemplatedPolicyNomadServer, }, + api.ACLTemplatedPolicyWorkloadIdentityName: { + TemplateID: ACLTemplatedPolicyWorkloadIdentityID, + TemplateName: api.ACLTemplatedPolicyWorkloadIdentityName, + Schema: ACLTemplatedPolicyWorkloadIdentitySchema, + Template: ACLTemplatedPolicyWorkloadIdentity, + }, } ) diff --git a/agent/structs/acl_templated_policy_ce.go b/agent/structs/acl_templated_policy_ce.go index 0783a259ab..cd9a52248a 100644 --- a/agent/structs/acl_templated_policy_ce.go +++ b/agent/structs/acl_templated_policy_ce.go @@ -19,6 +19,9 @@ var ACLTemplatedPolicyDNS string //go:embed acltemplatedpolicy/policies/ce/nomad-server.hcl var ACLTemplatedPolicyNomadServer string +//go:embed acltemplatedpolicy/policies/ce/workload-identity.hcl +var ACLTemplatedPolicyWorkloadIdentity string + func (t *ACLToken) TemplatedPolicyList() []*ACLTemplatedPolicy { if len(t.TemplatedPolicies) == 0 { return nil diff --git a/agent/structs/acl_templated_policy_ce_test.go b/agent/structs/acl_templated_policy_ce_test.go index 60cb1f887d..e160a911cb 100644 --- a/agent/structs/acl_templated_policy_ce_test.go +++ b/agent/structs/acl_templated_policy_ce_test.go @@ -8,8 +8,9 @@ package structs import ( "testing" - "github.com/hashicorp/consul/api" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/api" ) func TestStructs_ACLTemplatedPolicy_SyntheticPolicy(t *testing.T) { @@ -79,6 +80,21 @@ service_prefix "" { } query_prefix "" { 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" }`, }, }, diff --git a/agent/structs/acltemplatedpolicy/policies/ce/workload-identity.hcl b/agent/structs/acltemplatedpolicy/policies/ce/workload-identity.hcl new file mode 100644 index 0000000000..ccd1e05646 --- /dev/null +++ b/agent/structs/acltemplatedpolicy/policies/ce/workload-identity.hcl @@ -0,0 +1,3 @@ +identity "{{.Name}}" { + policy = "write" +} \ No newline at end of file diff --git a/agent/structs/acltemplatedpolicy/schemas/workload-identity.json b/agent/structs/acltemplatedpolicy/schemas/workload-identity.json new file mode 100644 index 0000000000..31064f36af --- /dev/null +++ b/agent/structs/acltemplatedpolicy/schemas/workload-identity.json @@ -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 + } + } +} \ No newline at end of file diff --git a/api/acl.go b/api/acl.go index f406001821..47b38eb6ca 100644 --- a/api/acl.go +++ b/api/acl.go @@ -21,10 +21,11 @@ const ( ACLManagementType = "management" // ACLTemplatedPolicy names - ACLTemplatedPolicyServiceName = "builtin/service" - ACLTemplatedPolicyNodeName = "builtin/node" - ACLTemplatedPolicyDNSName = "builtin/dns" - ACLTemplatedPolicyNomadServerName = "builtin/nomad-server" + ACLTemplatedPolicyServiceName = "builtin/service" + ACLTemplatedPolicyNodeName = "builtin/node" + ACLTemplatedPolicyDNSName = "builtin/dns" + ACLTemplatedPolicyNomadServerName = "builtin/nomad-server" + ACLTemplatedPolicyWorkloadIdentityName = "builtin/workload-identity" ) type ACLLink struct {