mirror of https://github.com/status-im/consul.git
[CE] Add workload bind type and templated policy (#19077)
This commit is contained in:
parent
ca4ff6ba1d
commit
ad26494016
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
acl: Adds workload identity templated policy
|
||||||
|
```
|
|
@ -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,
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
identity "{{.Name}}" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue