mirror of https://github.com/status-im/consul.git
[NET-5325] ACL templated policies support in tokens and roles (#18708)
* [NET-5325] ACL templated policies support in tokens and roles - Add API support for creating tokens/roles with templated-policies - Add CLI support for creating tokens/roles with templated-policies * adding changelog
This commit is contained in:
parent
993fe9a6a6
commit
bbef879f85
|
@ -0,0 +1,7 @@
|
||||||
|
```release-note:feature
|
||||||
|
acl: Added ACL Templated policies to simplify getting the right ACL token.
|
||||||
|
```
|
||||||
|
|
||||||
|
```release-note:improvement
|
||||||
|
cli: Added `-templated-policy`, `-templated-policy-file`, `-replace-templated-policy`, `-append-templated-policy`, `-replace-templated-policy-file`, `-append-templated-policy-file` and `-var` flags for creating or updating tokens/roles.
|
||||||
|
```
|
|
@ -102,6 +102,10 @@ func (id *missingIdentity) NodeIdentityList() []*structs.ACLNodeIdentity {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (id *missingIdentity) TemplatedPolicyList() []*structs.ACLTemplatedPolicy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (id *missingIdentity) IsExpired(asOf time.Time) bool {
|
func (id *missingIdentity) IsExpired(asOf time.Time) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -596,9 +600,11 @@ func (r *ACLResolver) resolvePoliciesForIdentity(identity structs.ACLIdentity) (
|
||||||
roleIDs = identity.RoleIDs()
|
roleIDs = identity.RoleIDs()
|
||||||
serviceIdentities = structs.ACLServiceIdentities(identity.ServiceIdentityList())
|
serviceIdentities = structs.ACLServiceIdentities(identity.ServiceIdentityList())
|
||||||
nodeIdentities = structs.ACLNodeIdentities(identity.NodeIdentityList())
|
nodeIdentities = structs.ACLNodeIdentities(identity.NodeIdentityList())
|
||||||
|
templatedPolicies = structs.ACLTemplatedPolicies(identity.TemplatedPolicyList())
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(policyIDs) == 0 && len(serviceIdentities) == 0 && len(roleIDs) == 0 && len(nodeIdentities) == 0 {
|
if len(policyIDs) == 0 && len(serviceIdentities) == 0 &&
|
||||||
|
len(roleIDs) == 0 && len(nodeIdentities) == 0 && len(templatedPolicies) == 0 {
|
||||||
// In this case the default policy will be all that is in effect.
|
// In this case the default policy will be all that is in effect.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -616,16 +622,19 @@ func (r *ACLResolver) resolvePoliciesForIdentity(identity structs.ACLIdentity) (
|
||||||
}
|
}
|
||||||
serviceIdentities = append(serviceIdentities, role.ServiceIdentities...)
|
serviceIdentities = append(serviceIdentities, role.ServiceIdentities...)
|
||||||
nodeIdentities = append(nodeIdentities, role.NodeIdentityList()...)
|
nodeIdentities = append(nodeIdentities, role.NodeIdentityList()...)
|
||||||
|
templatedPolicies = append(templatedPolicies, role.TemplatedPolicyList()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now deduplicate any policies or service identities that occur more than once.
|
// Now deduplicate any policies or service identities that occur more than once.
|
||||||
policyIDs = dedupeStringSlice(policyIDs)
|
policyIDs = dedupeStringSlice(policyIDs)
|
||||||
serviceIdentities = serviceIdentities.Deduplicate()
|
serviceIdentities = serviceIdentities.Deduplicate()
|
||||||
nodeIdentities = nodeIdentities.Deduplicate()
|
nodeIdentities = nodeIdentities.Deduplicate()
|
||||||
|
templatedPolicies = templatedPolicies.Deduplicate()
|
||||||
|
|
||||||
// Generate synthetic policies for all service identities in effect.
|
// Generate synthetic policies for all service identities in effect.
|
||||||
syntheticPolicies := r.synthesizePoliciesForServiceIdentities(serviceIdentities, identity.EnterpriseMetadata())
|
syntheticPolicies := r.synthesizePoliciesForServiceIdentities(serviceIdentities, identity.EnterpriseMetadata())
|
||||||
syntheticPolicies = append(syntheticPolicies, r.synthesizePoliciesForNodeIdentities(nodeIdentities, identity.EnterpriseMetadata())...)
|
syntheticPolicies = append(syntheticPolicies, r.synthesizePoliciesForNodeIdentities(nodeIdentities, identity.EnterpriseMetadata())...)
|
||||||
|
syntheticPolicies = append(syntheticPolicies, r.synthesizePoliciesForTemplatedPolicies(templatedPolicies, identity.EnterpriseMetadata())...)
|
||||||
|
|
||||||
// For the new ACLs policy replication is mandatory for correct operation on servers. Therefore
|
// For the new ACLs policy replication is mandatory for correct operation on servers. Therefore
|
||||||
// we only attempt to resolve policies locally
|
// we only attempt to resolve policies locally
|
||||||
|
@ -669,6 +678,24 @@ func (r *ACLResolver) synthesizePoliciesForNodeIdentities(nodeIdentities []*stru
|
||||||
return syntheticPolicies
|
return syntheticPolicies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ACLResolver) synthesizePoliciesForTemplatedPolicies(templatedPolicies []*structs.ACLTemplatedPolicy, entMeta *acl.EnterpriseMeta) []*structs.ACLPolicy {
|
||||||
|
if len(templatedPolicies) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
syntheticPolicies := make([]*structs.ACLPolicy, 0, len(templatedPolicies))
|
||||||
|
for _, tp := range templatedPolicies {
|
||||||
|
policy, err := tp.SyntheticPolicy(entMeta)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.Warn(fmt.Sprintf("could not generate synthetic policy for templated policy: %q", tp.TemplateName), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
syntheticPolicies = append(syntheticPolicies, policy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return syntheticPolicies
|
||||||
|
}
|
||||||
|
|
||||||
func mergeStringSlice(a, b []string) []string {
|
func mergeStringSlice(a, b []string) []string {
|
||||||
out := make([]string, 0, len(a)+len(b))
|
out := make([]string, 0, len(a)+len(b))
|
||||||
out = append(out, a...)
|
out = append(out, a...)
|
||||||
|
|
|
@ -350,9 +350,10 @@ func (a *ACL) lookupExpandedTokenInfo(ws memdb.WatchSet, state *state.Store, tok
|
||||||
policyIDs := make(map[string]struct{})
|
policyIDs := make(map[string]struct{})
|
||||||
roleIDs := make(map[string]struct{})
|
roleIDs := make(map[string]struct{})
|
||||||
identityPolicies := make(map[string]*structs.ACLPolicy)
|
identityPolicies := make(map[string]*structs.ACLPolicy)
|
||||||
|
templatedPolicies := make(map[string]*structs.ACLPolicy)
|
||||||
tokenInfo := structs.ExpandedTokenInfo{}
|
tokenInfo := structs.ExpandedTokenInfo{}
|
||||||
|
|
||||||
// Add the token's policies and node/service identity policies
|
// Add the token's policies, templated policies and node/service identity policies
|
||||||
for _, policy := range token.Policies {
|
for _, policy := range token.Policies {
|
||||||
policyIDs[policy.ID] = struct{}{}
|
policyIDs[policy.ID] = struct{}{}
|
||||||
}
|
}
|
||||||
|
@ -368,6 +369,14 @@ func (a *ACL) lookupExpandedTokenInfo(ws memdb.WatchSet, state *state.Store, tok
|
||||||
policy := identity.SyntheticPolicy(&token.EnterpriseMeta)
|
policy := identity.SyntheticPolicy(&token.EnterpriseMeta)
|
||||||
identityPolicies[policy.ID] = policy
|
identityPolicies[policy.ID] = policy
|
||||||
}
|
}
|
||||||
|
for _, templatedPolicy := range token.TemplatedPolicies {
|
||||||
|
policy, err := templatedPolicy.SyntheticPolicy(&token.EnterpriseMeta)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn(fmt.Sprintf("could not generate synthetic policy for templated policy: %q", templatedPolicy.TemplateName), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
templatedPolicies[policy.ID] = policy
|
||||||
|
}
|
||||||
|
|
||||||
// Get any namespace default roles/policies to look up
|
// Get any namespace default roles/policies to look up
|
||||||
nsPolicies, nsRoles, err := getTokenNamespaceDefaults(ws, state, &token.EnterpriseMeta)
|
nsPolicies, nsRoles, err := getTokenNamespaceDefaults(ws, state, &token.EnterpriseMeta)
|
||||||
|
@ -405,6 +414,14 @@ func (a *ACL) lookupExpandedTokenInfo(ws memdb.WatchSet, state *state.Store, tok
|
||||||
policy := identity.SyntheticPolicy(&role.EnterpriseMeta)
|
policy := identity.SyntheticPolicy(&role.EnterpriseMeta)
|
||||||
identityPolicies[policy.ID] = policy
|
identityPolicies[policy.ID] = policy
|
||||||
}
|
}
|
||||||
|
for _, templatedPolicy := range role.TemplatedPolicies {
|
||||||
|
policy, err := templatedPolicy.SyntheticPolicy(&role.EnterpriseMeta)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn(fmt.Sprintf("could not generate synthetic policy for templated policy: %q", templatedPolicy.TemplateName), "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
templatedPolicies[policy.ID] = policy
|
||||||
|
}
|
||||||
|
|
||||||
tokenInfo.ExpandedRoles = append(tokenInfo.ExpandedRoles, role)
|
tokenInfo.ExpandedRoles = append(tokenInfo.ExpandedRoles, role)
|
||||||
}
|
}
|
||||||
|
@ -423,6 +440,9 @@ func (a *ACL) lookupExpandedTokenInfo(ws memdb.WatchSet, state *state.Store, tok
|
||||||
for _, policy := range identityPolicies {
|
for _, policy := range identityPolicies {
|
||||||
policies = append(policies, policy)
|
policies = append(policies, policy)
|
||||||
}
|
}
|
||||||
|
for _, policy := range templatedPolicies {
|
||||||
|
policies = append(policies, policy)
|
||||||
|
}
|
||||||
|
|
||||||
tokenInfo.ExpandedPolicies = policies
|
tokenInfo.ExpandedPolicies = policies
|
||||||
tokenInfo.AgentACLDefaultPolicy = a.srv.config.ACLResolverSettings.ACLDefaultPolicy
|
tokenInfo.AgentACLDefaultPolicy = a.srv.config.ACLResolverSettings.ACLDefaultPolicy
|
||||||
|
@ -486,6 +506,7 @@ func (a *ACL) TokenClone(args *structs.ACLTokenSetRequest, reply *structs.ACLTok
|
||||||
Roles: token.Roles,
|
Roles: token.Roles,
|
||||||
ServiceIdentities: token.ServiceIdentities,
|
ServiceIdentities: token.ServiceIdentities,
|
||||||
NodeIdentities: token.NodeIdentities,
|
NodeIdentities: token.NodeIdentities,
|
||||||
|
TemplatedPolicies: token.TemplatedPolicies,
|
||||||
Local: token.Local,
|
Local: token.Local,
|
||||||
Description: token.Description,
|
Description: token.Description,
|
||||||
ExpirationTime: token.ExpirationTime,
|
ExpirationTime: token.ExpirationTime,
|
||||||
|
@ -1364,6 +1385,27 @@ func (a *ACL) RoleSet(args *structs.ACLRoleSetRequest, reply *structs.ACLRole) e
|
||||||
}
|
}
|
||||||
role.NodeIdentities = role.NodeIdentities.Deduplicate()
|
role.NodeIdentities = role.NodeIdentities.Deduplicate()
|
||||||
|
|
||||||
|
for _, templatedPolicy := range role.TemplatedPolicies {
|
||||||
|
if templatedPolicy.TemplateName == "" {
|
||||||
|
return fmt.Errorf("templated policy is missing the template name field on this role")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTemplate, ok := structs.GetACLTemplatedPolicyBase(templatedPolicy.TemplateName)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("templated policy with an invalid templated name: %s for this role", templatedPolicy.TemplateName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if templatedPolicy.TemplateID == "" {
|
||||||
|
templatedPolicy.TemplateID = baseTemplate.TemplateID
|
||||||
|
}
|
||||||
|
|
||||||
|
err := templatedPolicy.ValidateTemplatedPolicy(baseTemplate.Schema)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encountered role with invalid templated policy: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
role.TemplatedPolicies = role.TemplatedPolicies.Deduplicate()
|
||||||
|
|
||||||
// calculate the hash for this role
|
// calculate the hash for this role
|
||||||
role.SetHash(true)
|
role.SetHash(true)
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/consul/authmethod/testauth"
|
"github.com/hashicorp/consul/agent/consul/authmethod/testauth"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/agent/structs/aclfilter"
|
"github.com/hashicorp/consul/agent/structs/aclfilter"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest"
|
"github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
|
@ -376,7 +377,7 @@ func TestACLEndpoint_TokenRead(t *testing.T) {
|
||||||
require.ElementsMatch(t, []*structs.ACLRole{r1, r2}, resp.ExpandedRoles)
|
require.ElementsMatch(t, []*structs.ACLRole{r1, r2}, resp.ExpandedRoles)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("expanded output with node/service identities", func(t *testing.T) {
|
t.Run("expanded output with node/service identities and templated policies", func(t *testing.T) {
|
||||||
setReq := structs.ACLTokenSetRequest{
|
setReq := structs.ACLTokenSetRequest{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
ACLToken: structs.ACLToken{
|
ACLToken: structs.ACLToken{
|
||||||
|
@ -401,6 +402,22 @@ func TestACLEndpoint_TokenRead(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*structs.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"dc1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "foo",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"dc1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
Local: false,
|
Local: false,
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
|
@ -414,6 +431,11 @@ func TestACLEndpoint_TokenRead(t *testing.T) {
|
||||||
for _, serviceIdentity := range setReq.ACLToken.NodeIdentities {
|
for _, serviceIdentity := range setReq.ACLToken.NodeIdentities {
|
||||||
expectedPolicies = append(expectedPolicies, serviceIdentity.SyntheticPolicy(entMeta))
|
expectedPolicies = append(expectedPolicies, serviceIdentity.SyntheticPolicy(entMeta))
|
||||||
}
|
}
|
||||||
|
for _, templatedPolicy := range setReq.ACLToken.TemplatedPolicies {
|
||||||
|
pol, tmplError := templatedPolicy.SyntheticPolicy(entMeta)
|
||||||
|
require.NoError(t, tmplError)
|
||||||
|
expectedPolicies = append(expectedPolicies, pol)
|
||||||
|
}
|
||||||
|
|
||||||
setResp := structs.ACLToken{}
|
setResp := structs.ACLToken{}
|
||||||
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenSet", &setReq, &setResp)
|
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenSet", &setReq, &setResp)
|
||||||
|
@ -468,6 +490,10 @@ func TestACLEndpoint_TokenClone(t *testing.T) {
|
||||||
t.NodeIdentities = []*structs.ACLNodeIdentity{
|
t.NodeIdentities = []*structs.ACLNodeIdentity{
|
||||||
{NodeName: "foo", Datacenter: "bar"},
|
{NodeName: "foo", Datacenter: "bar"},
|
||||||
}
|
}
|
||||||
|
t.TemplatedPolicies = []*structs.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "foo"}, Datacenters: []string{"bar"}},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "node"}},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -490,6 +516,7 @@ func TestACLEndpoint_TokenClone(t *testing.T) {
|
||||||
require.Equal(t, t1.Roles, t2.Roles)
|
require.Equal(t, t1.Roles, t2.Roles)
|
||||||
require.Equal(t, t1.ServiceIdentities, t2.ServiceIdentities)
|
require.Equal(t, t1.ServiceIdentities, t2.ServiceIdentities)
|
||||||
require.Equal(t, t1.NodeIdentities, t2.NodeIdentities)
|
require.Equal(t, t1.NodeIdentities, t2.NodeIdentities)
|
||||||
|
require.Equal(t, t1.TemplatedPolicies, t2.TemplatedPolicies)
|
||||||
require.Equal(t, t1.Local, t2.Local)
|
require.Equal(t, t1.Local, t2.Local)
|
||||||
require.NotEqual(t, t1.AccessorID, t2.AccessorID)
|
require.NotEqual(t, t1.AccessorID, t2.AccessorID)
|
||||||
require.NotEqual(t, t1.SecretID, t2.SecretID)
|
require.NotEqual(t, t1.SecretID, t2.SecretID)
|
||||||
|
@ -548,6 +575,10 @@ func TestACLEndpoint_TokenSet(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*structs.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "foo"}, Datacenters: []string{"bar"}},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "node"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
}
|
}
|
||||||
|
@ -570,6 +601,19 @@ func TestACLEndpoint_TokenSet(t *testing.T) {
|
||||||
require.Equal(t, "foo", token.NodeIdentities[0].NodeName)
|
require.Equal(t, "foo", token.NodeIdentities[0].NodeName)
|
||||||
require.Equal(t, "dc1", token.NodeIdentities[0].Datacenter)
|
require.Equal(t, "dc1", token.NodeIdentities[0].Datacenter)
|
||||||
|
|
||||||
|
require.Len(t, token.TemplatedPolicies, 2)
|
||||||
|
require.Contains(t, token.TemplatedPolicies, &structs.ACLTemplatedPolicy{
|
||||||
|
TemplateID: structs.ACLTemplatedPolicyServiceID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "foo"},
|
||||||
|
Datacenters: []string{"bar"},
|
||||||
|
})
|
||||||
|
require.Contains(t, token.TemplatedPolicies, &structs.ACLTemplatedPolicy{
|
||||||
|
TemplateID: structs.ACLTemplatedPolicyNodeID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "node"},
|
||||||
|
})
|
||||||
|
|
||||||
accessorID = token.AccessorID
|
accessorID = token.AccessorID
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2183,6 +2227,39 @@ func TestACLEndpoint_PolicySet_CustomID(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestACLEndpoint_TemplatedPolicySet_UnknownTemplateName(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, srv, _ := testACLServerWithConfig(t, nil, false)
|
||||||
|
waitForLeaderEstablishment(t, srv)
|
||||||
|
|
||||||
|
aclEp := ACL{srv: srv}
|
||||||
|
|
||||||
|
t.Run("unknown template name", func(t *testing.T) {
|
||||||
|
req := structs.ACLTokenSetRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
ACLToken: structs.ACLToken{
|
||||||
|
Description: "foobar",
|
||||||
|
Policies: nil,
|
||||||
|
Local: false,
|
||||||
|
TemplatedPolicies: []*structs.ACLTemplatedPolicy{{TemplateName: "fake-builtin"}},
|
||||||
|
},
|
||||||
|
Create: true,
|
||||||
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := structs.ACLToken{}
|
||||||
|
|
||||||
|
err := aclEp.TokenSet(&req, &resp)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "no such ACL templated policy with Name \"fake-builtin\"")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestACLEndpoint_PolicySet_builtins(t *testing.T) {
|
func TestACLEndpoint_PolicySet_builtins(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/hashicorp/consul/acl/resolver"
|
"github.com/hashicorp/consul/acl/resolver"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/agent/token"
|
"github.com/hashicorp/consul/agent/token"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
)
|
)
|
||||||
|
@ -1978,6 +1979,48 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&structs.ACLToken{
|
||||||
|
AccessorID: "359b9927-25fd-46b9-84c2-3470f848ec65",
|
||||||
|
SecretID: "found-synthetic-policy-5",
|
||||||
|
TemplatedPolicies: []*structs.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "templated-test-node1",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"dc1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "templated-test-node2",
|
||||||
|
},
|
||||||
|
// as the resolver is in dc1 this identity should be ignored
|
||||||
|
Datacenters: []string{"dc2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.ACLToken{
|
||||||
|
AccessorID: "359b9927-25fd-46b9-84c2-3470f848ec65",
|
||||||
|
SecretID: "found-synthetic-policy-6",
|
||||||
|
TemplatedPolicies: []*structs.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "templated-test-node3",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"dc1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "templated-test-node4",
|
||||||
|
},
|
||||||
|
// as the resolver is in dc1 this identity should be ignored
|
||||||
|
Datacenters: []string{"dc2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// We resolve these tokens in the same cache session
|
// We resolve these tokens in the same cache session
|
||||||
|
@ -2043,6 +2086,22 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega
|
||||||
// ensure node identity for other DC is ignored
|
// ensure node identity for other DC is ignored
|
||||||
require.Equal(t, acl.Deny, authz.NodeWrite("test-node-dc2", nil))
|
require.Equal(t, acl.Deny, authz.NodeWrite("test-node-dc2", nil))
|
||||||
})
|
})
|
||||||
|
t.Run("synthetic-policy-6", func(t *testing.T) { // templated policy
|
||||||
|
authz, err := r.ResolveToken("found-synthetic-policy-6")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, authz)
|
||||||
|
|
||||||
|
// spot check some random perms
|
||||||
|
require.Equal(t, acl.Deny, authz.ACLRead(nil))
|
||||||
|
require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil))
|
||||||
|
// ensure we didn't bleed over to the other synthetic policy
|
||||||
|
require.Equal(t, acl.Deny, authz.NodeWrite("templated-test-node1", nil))
|
||||||
|
// check our own synthetic policy
|
||||||
|
require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil))
|
||||||
|
require.Equal(t, acl.Allow, authz.NodeWrite("templated-test-node3", nil))
|
||||||
|
// ensure template identity for other DC is ignored
|
||||||
|
require.Equal(t, acl.Deny, authz.NodeWrite("templated-test-node4", nil))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
runTwiceAndReset("Anonymous", func(t *testing.T) {
|
runTwiceAndReset("Anonymous", func(t *testing.T) {
|
||||||
|
|
|
@ -309,6 +309,12 @@ func (w *TokenWriter) write(token, existing *structs.ACLToken, fromLogin bool) (
|
||||||
}
|
}
|
||||||
token.NodeIdentities = nodeIdentities
|
token.NodeIdentities = nodeIdentities
|
||||||
|
|
||||||
|
templatedPolicies, err := w.normalizeTemplatedPolicies(token.TemplatedPolicies)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token.TemplatedPolicies = templatedPolicies
|
||||||
|
|
||||||
if err := w.enterpriseValidation(token, existing); err != nil {
|
if err := w.enterpriseValidation(token, existing); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -442,3 +448,32 @@ func (w *TokenWriter) normalizeNodeIdentities(nodeIDs structs.ACLNodeIdentities)
|
||||||
}
|
}
|
||||||
return nodeIDs.Deduplicate(), nil
|
return nodeIDs.Deduplicate(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TokenWriter) normalizeTemplatedPolicies(templatedPolicies structs.ACLTemplatedPolicies) (structs.ACLTemplatedPolicies, error) {
|
||||||
|
if len(templatedPolicies) == 0 {
|
||||||
|
return templatedPolicies, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPolicies := make(structs.ACLTemplatedPolicies, 0, len(templatedPolicies))
|
||||||
|
for _, templatedPolicy := range templatedPolicies {
|
||||||
|
if templatedPolicy.TemplateName == "" {
|
||||||
|
return nil, errors.New("templated policy is missing the template name field on this token")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp, ok := structs.GetACLTemplatedPolicyBase(templatedPolicy.TemplateName)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no such ACL templated policy with Name %q", templatedPolicy.TemplateName)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := templatedPolicy.Clone()
|
||||||
|
out.TemplateID = tmp.TemplateID
|
||||||
|
|
||||||
|
err := templatedPolicy.ValidateTemplatedPolicy(tmp.Schema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("validation error for templated policy %q: %w", templatedPolicy.TemplateName, err)
|
||||||
|
}
|
||||||
|
finalPolicies = append(finalPolicies, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalPolicies.Deduplicate(), nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/consul/state"
|
"github.com/hashicorp/consul/agent/consul/state"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTokenWriter_Create_Validation(t *testing.T) {
|
func TestTokenWriter_Create_Validation(t *testing.T) {
|
||||||
|
@ -357,6 +358,59 @@ func TestTokenWriter_NodeIdentities(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTokenWriter_TemplatedPolicies(t *testing.T) {
|
||||||
|
aclCache := &MockACLCache{}
|
||||||
|
aclCache.On("RemoveIdentityWithSecretToken", mock.Anything)
|
||||||
|
|
||||||
|
store := testStateStore(t)
|
||||||
|
|
||||||
|
writer := buildTokenWriter(store, aclCache)
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input []*structs.ACLTemplatedPolicy
|
||||||
|
output []*structs.ACLTemplatedPolicy
|
||||||
|
errorContains string
|
||||||
|
}{
|
||||||
|
"missing templated policy name": {
|
||||||
|
input: []*structs.ACLTemplatedPolicy{{TemplateName: ""}},
|
||||||
|
errorContains: "templated policy is missing the template name field on this token",
|
||||||
|
},
|
||||||
|
"invalid templated policy name": {
|
||||||
|
input: []*structs.ACLTemplatedPolicy{{TemplateName: "faketemplate"}},
|
||||||
|
errorContains: "no such ACL templated policy with Name \"faketemplate\"",
|
||||||
|
},
|
||||||
|
"missing required template variable: name": {
|
||||||
|
input: []*structs.ACLTemplatedPolicy{{TemplateName: api.ACLTemplatedPolicyServiceName, Datacenters: []string{"dc1"}}},
|
||||||
|
errorContains: "validation error for templated policy \"builtin/service\"",
|
||||||
|
},
|
||||||
|
"duplicate templated policies are removed and ids are set": {
|
||||||
|
input: []*structs.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "web"}},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "web"}},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "api"}},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "nodename"}},
|
||||||
|
},
|
||||||
|
output: []*structs.ACLTemplatedPolicy{
|
||||||
|
{TemplateID: structs.ACLTemplatedPolicyServiceID, TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "web"}},
|
||||||
|
{TemplateID: structs.ACLTemplatedPolicyServiceID, TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "api"}},
|
||||||
|
{TemplateID: structs.ACLTemplatedPolicyNodeID, TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &structs.ACLTemplatedPolicyVariables{Name: "nodename"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for desc, tc := range testCases {
|
||||||
|
t.Run(desc, func(t *testing.T) {
|
||||||
|
updated, err := writer.Create(&structs.ACLToken{TemplatedPolicies: tc.input}, false)
|
||||||
|
if tc.errorContains == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.ElementsMatch(t, tc.output, updated.TemplatedPolicies)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), tc.errorContains)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTokenWriter_Create_Expiration(t *testing.T) {
|
func TestTokenWriter_Create_Expiration(t *testing.T) {
|
||||||
aclCache := &MockACLCache{}
|
aclCache := &MockACLCache{}
|
||||||
aclCache.On("RemoveIdentityWithSecretToken", mock.Anything)
|
aclCache.On("RemoveIdentityWithSecretToken", mock.Anything)
|
||||||
|
|
|
@ -1177,6 +1177,26 @@ func aclRoleSetTxn(tx WriteTxn, idx uint64, role *structs.ACLRole, allowMissing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, templatedPolicy := range role.TemplatedPolicies {
|
||||||
|
if templatedPolicy.TemplateName == "" {
|
||||||
|
return fmt.Errorf("encountered a Role %s (%s) with an empty templated policy name in the state store", role.Name, role.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseTemplate, ok := structs.GetACLTemplatedPolicyBase(templatedPolicy.TemplateName)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("encountered a Role %s (%s) with an invalid templated policy name %q", role.Name, role.ID, templatedPolicy.TemplateName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if templatedPolicy.TemplateID == "" {
|
||||||
|
templatedPolicy.TemplateID = baseTemplate.TemplateID
|
||||||
|
}
|
||||||
|
|
||||||
|
err := templatedPolicy.ValidateTemplatedPolicy(baseTemplate.Schema)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encountered a Role %s (%s) with an invalid templated policy: %w", role.Name, role.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := aclRoleUpsertValidateEnterprise(tx, role, existing); err != nil {
|
if err := aclRoleUpsertValidateEnterprise(tx, role, existing); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ type ACLIdentity interface {
|
||||||
RoleIDs() []string
|
RoleIDs() []string
|
||||||
ServiceIdentityList() []*ACLServiceIdentity
|
ServiceIdentityList() []*ACLServiceIdentity
|
||||||
NodeIdentityList() []*ACLNodeIdentity
|
NodeIdentityList() []*ACLNodeIdentity
|
||||||
|
TemplatedPolicyList() []*ACLTemplatedPolicy
|
||||||
IsExpired(asOf time.Time) bool
|
IsExpired(asOf time.Time) bool
|
||||||
IsLocal() bool
|
IsLocal() bool
|
||||||
EnterpriseMetadata() *acl.EnterpriseMeta
|
EnterpriseMetadata() *acl.EnterpriseMeta
|
||||||
|
@ -314,6 +315,9 @@ type ACLToken struct {
|
||||||
// The node identities that this token should be allowed to manage.
|
// The node identities that this token should be allowed to manage.
|
||||||
NodeIdentities ACLNodeIdentities `json:",omitempty"`
|
NodeIdentities ACLNodeIdentities `json:",omitempty"`
|
||||||
|
|
||||||
|
// The templated policies to generate synthetic policies for.
|
||||||
|
TemplatedPolicies ACLTemplatedPolicies `json:",omitempty"`
|
||||||
|
|
||||||
// Whether this token is DC local. This means that it will not be synced
|
// Whether this token is DC local. This means that it will not be synced
|
||||||
// to the ACL datacenter and replicated to others.
|
// to the ACL datacenter and replicated to others.
|
||||||
Local bool
|
Local bool
|
||||||
|
@ -394,6 +398,7 @@ func (t *ACLToken) Clone() *ACLToken {
|
||||||
t2.Roles = nil
|
t2.Roles = nil
|
||||||
t2.ServiceIdentities = nil
|
t2.ServiceIdentities = nil
|
||||||
t2.NodeIdentities = nil
|
t2.NodeIdentities = nil
|
||||||
|
t2.TemplatedPolicies = nil
|
||||||
|
|
||||||
if len(t.Policies) > 0 {
|
if len(t.Policies) > 0 {
|
||||||
t2.Policies = make([]ACLTokenPolicyLink, len(t.Policies))
|
t2.Policies = make([]ACLTokenPolicyLink, len(t.Policies))
|
||||||
|
@ -415,6 +420,12 @@ func (t *ACLToken) Clone() *ACLToken {
|
||||||
t2.NodeIdentities[i] = n.Clone()
|
t2.NodeIdentities[i] = n.Clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(t.TemplatedPolicies) > 0 {
|
||||||
|
t2.TemplatedPolicies = make([]*ACLTemplatedPolicy, len(t.TemplatedPolicies))
|
||||||
|
for idx, tp := range t.TemplatedPolicies {
|
||||||
|
t2.TemplatedPolicies[idx] = tp.Clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &t2
|
return &t2
|
||||||
}
|
}
|
||||||
|
@ -523,6 +534,10 @@ func (t *ACLToken) SetHash(force bool) []byte {
|
||||||
nodeID.AddToHash(hash)
|
nodeID.AddToHash(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, templatedPolicy := range t.TemplatedPolicies {
|
||||||
|
templatedPolicy.AddToHash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
t.EnterpriseMeta.AddToHash(hash, false)
|
t.EnterpriseMeta.AddToHash(hash, false)
|
||||||
|
|
||||||
// Finalize the hash
|
// Finalize the hash
|
||||||
|
@ -549,6 +564,9 @@ func (t *ACLToken) EstimateSize() int {
|
||||||
for _, nodeID := range t.NodeIdentities {
|
for _, nodeID := range t.NodeIdentities {
|
||||||
size += nodeID.EstimateSize()
|
size += nodeID.EstimateSize()
|
||||||
}
|
}
|
||||||
|
for _, templatedPolicy := range t.TemplatedPolicies {
|
||||||
|
size += templatedPolicy.EstimateSize()
|
||||||
|
}
|
||||||
return size + t.EnterpriseMeta.EstimateSize()
|
return size + t.EnterpriseMeta.EstimateSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,6 +581,7 @@ type ACLTokenListStub struct {
|
||||||
Roles []ACLTokenRoleLink `json:",omitempty"`
|
Roles []ACLTokenRoleLink `json:",omitempty"`
|
||||||
ServiceIdentities ACLServiceIdentities `json:",omitempty"`
|
ServiceIdentities ACLServiceIdentities `json:",omitempty"`
|
||||||
NodeIdentities ACLNodeIdentities `json:",omitempty"`
|
NodeIdentities ACLNodeIdentities `json:",omitempty"`
|
||||||
|
TemplatedPolicies ACLTemplatedPolicies `json:",omitempty"`
|
||||||
Local bool
|
Local bool
|
||||||
AuthMethod string `json:",omitempty"`
|
AuthMethod string `json:",omitempty"`
|
||||||
ExpirationTime *time.Time `json:",omitempty"`
|
ExpirationTime *time.Time `json:",omitempty"`
|
||||||
|
@ -585,6 +604,7 @@ func (token *ACLToken) Stub() *ACLTokenListStub {
|
||||||
Roles: token.Roles,
|
Roles: token.Roles,
|
||||||
ServiceIdentities: token.ServiceIdentities,
|
ServiceIdentities: token.ServiceIdentities,
|
||||||
NodeIdentities: token.NodeIdentities,
|
NodeIdentities: token.NodeIdentities,
|
||||||
|
TemplatedPolicies: token.TemplatedPolicies,
|
||||||
Local: token.Local,
|
Local: token.Local,
|
||||||
AuthMethod: token.AuthMethod,
|
AuthMethod: token.AuthMethod,
|
||||||
ExpirationTime: token.ExpirationTime,
|
ExpirationTime: token.ExpirationTime,
|
||||||
|
@ -870,6 +890,9 @@ type ACLRole struct {
|
||||||
// List of nodes to generate synthetic policies for.
|
// List of nodes to generate synthetic policies for.
|
||||||
NodeIdentities ACLNodeIdentities `json:",omitempty"`
|
NodeIdentities ACLNodeIdentities `json:",omitempty"`
|
||||||
|
|
||||||
|
// List of templated policies to generate synthethic policies for.
|
||||||
|
TemplatedPolicies ACLTemplatedPolicies `json:",omitempty"`
|
||||||
|
|
||||||
// Hash of the contents of the role
|
// Hash of the contents of the role
|
||||||
// This does not take into account the ID (which is immutable)
|
// This does not take into account the ID (which is immutable)
|
||||||
// nor the raft metadata.
|
// nor the raft metadata.
|
||||||
|
@ -909,6 +932,7 @@ func (r *ACLRole) Clone() *ACLRole {
|
||||||
r2.Policies = nil
|
r2.Policies = nil
|
||||||
r2.ServiceIdentities = nil
|
r2.ServiceIdentities = nil
|
||||||
r2.NodeIdentities = nil
|
r2.NodeIdentities = nil
|
||||||
|
r2.TemplatedPolicies = nil
|
||||||
|
|
||||||
if len(r.Policies) > 0 {
|
if len(r.Policies) > 0 {
|
||||||
r2.Policies = make([]ACLRolePolicyLink, len(r.Policies))
|
r2.Policies = make([]ACLRolePolicyLink, len(r.Policies))
|
||||||
|
@ -926,6 +950,12 @@ func (r *ACLRole) Clone() *ACLRole {
|
||||||
r2.NodeIdentities[i] = n.Clone()
|
r2.NodeIdentities[i] = n.Clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(r.TemplatedPolicies) > 0 {
|
||||||
|
r2.TemplatedPolicies = make([]*ACLTemplatedPolicy, len(r.TemplatedPolicies))
|
||||||
|
for i, n := range r.TemplatedPolicies {
|
||||||
|
r2.TemplatedPolicies[i] = n.Clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
return &r2
|
return &r2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -957,6 +987,9 @@ func (r *ACLRole) SetHash(force bool) []byte {
|
||||||
for _, nodeID := range r.NodeIdentities {
|
for _, nodeID := range r.NodeIdentities {
|
||||||
nodeID.AddToHash(hash)
|
nodeID.AddToHash(hash)
|
||||||
}
|
}
|
||||||
|
for _, templatedPolicy := range r.TemplatedPolicies {
|
||||||
|
templatedPolicy.AddToHash(hash)
|
||||||
|
}
|
||||||
|
|
||||||
r.EnterpriseMeta.AddToHash(hash, false)
|
r.EnterpriseMeta.AddToHash(hash, false)
|
||||||
|
|
||||||
|
@ -984,6 +1017,9 @@ func (r *ACLRole) EstimateSize() int {
|
||||||
for _, nodeID := range r.NodeIdentities {
|
for _, nodeID := range r.NodeIdentities {
|
||||||
size += nodeID.EstimateSize()
|
size += nodeID.EstimateSize()
|
||||||
}
|
}
|
||||||
|
for _, templatedPolicy := range r.TemplatedPolicies {
|
||||||
|
size += templatedPolicy.EstimateSize()
|
||||||
|
}
|
||||||
|
|
||||||
return size + r.EnterpriseMeta.EstimateSize()
|
return size + r.EnterpriseMeta.EstimateSize()
|
||||||
}
|
}
|
||||||
|
@ -1845,6 +1881,10 @@ func (id *AgentRecoveryTokenIdentity) NodeIdentityList() []*ACLNodeIdentity {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (id *AgentRecoveryTokenIdentity) TemplatedPolicyList() []*ACLTemplatedPolicy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (id *AgentRecoveryTokenIdentity) IsExpired(asOf time.Time) bool {
|
func (id *AgentRecoveryTokenIdentity) IsExpired(asOf time.Time) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1893,6 +1933,10 @@ func (i *ACLServerIdentity) NodeIdentityList() []*ACLNodeIdentity {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ACLServerIdentity) TemplatedPolicyList() []*ACLTemplatedPolicy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ACLServerIdentity) IsExpired(asOf time.Time) bool {
|
func (i *ACLServerIdentity) IsExpired(asOf time.Time) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ACLTemplatedPolicies []*ACLTemplatedPolicy
|
||||||
|
|
||||||
|
const (
|
||||||
|
ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004"
|
||||||
|
ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003"
|
||||||
|
ACLTemplatedPolicyIdentitiesSchema = `{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "$ref": "#/definitions/min-length-one" }
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"definitions": {
|
||||||
|
"min-length-one": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005"
|
||||||
|
ACLTemplatedPolicyDNSSchema = "" // empty schema as it does not require variables
|
||||||
|
)
|
||||||
|
|
||||||
|
// ACLTemplatedPolicyBase contains basic information about builtin templated policies
|
||||||
|
// template name, id, template code and schema
|
||||||
|
type ACLTemplatedPolicyBase struct {
|
||||||
|
TemplateName string
|
||||||
|
TemplateID string
|
||||||
|
Schema string
|
||||||
|
Template string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// TODO(Ronald): add other templates
|
||||||
|
// This supports: node, service and dns templates
|
||||||
|
aclTemplatedPoliciesList = map[string]*ACLTemplatedPolicyBase{
|
||||||
|
api.ACLTemplatedPolicyServiceName: {
|
||||||
|
TemplateID: ACLTemplatedPolicyServiceID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
Schema: ACLTemplatedPolicyIdentitiesSchema,
|
||||||
|
Template: ACLTemplatedPolicyService,
|
||||||
|
},
|
||||||
|
api.ACLTemplatedPolicyNodeName: {
|
||||||
|
TemplateID: ACLTemplatedPolicyNodeID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
Schema: ACLTemplatedPolicyIdentitiesSchema,
|
||||||
|
Template: ACLTemplatedPolicyNode,
|
||||||
|
},
|
||||||
|
api.ACLTemplatedPolicyDNSName: {
|
||||||
|
TemplateID: ACLTemplatedPolicyDNSID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyDNSName,
|
||||||
|
Schema: ACLTemplatedPolicyDNSSchema,
|
||||||
|
Template: ACLTemplatedPolicyDNS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ACLTemplatedPolicy represents a template used to generate a `synthetic` policy
|
||||||
|
// given some input variables.
|
||||||
|
type ACLTemplatedPolicy struct {
|
||||||
|
// TemplateID are hidden from all displays and should not be exposed to the users.
|
||||||
|
TemplateID string `json:",omitempty"`
|
||||||
|
|
||||||
|
// TemplateName is used for display purposes mostly and should not be used for policy rendering.
|
||||||
|
TemplateName string `json:",omitempty"`
|
||||||
|
|
||||||
|
// TemplateVariables are input variables required to render templated policies.
|
||||||
|
TemplateVariables *ACLTemplatedPolicyVariables `json:",omitempty"`
|
||||||
|
|
||||||
|
// Datacenters that the synthetic policy will be valid within.
|
||||||
|
// - No wildcards allowed
|
||||||
|
// - If empty then the synthetic policy is valid within all datacenters
|
||||||
|
//
|
||||||
|
// This is kept for legacy reasons to enable us to replace Node/Service Identities by templated policies.
|
||||||
|
//
|
||||||
|
// Only valid for global tokens. It is an error to specify this for local tokens.
|
||||||
|
Datacenters []string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACLTemplatedPolicyVariables are input variables required to render templated policies.
|
||||||
|
type ACLTemplatedPolicyVariables struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *ACLTemplatedPolicy) Clone() *ACLTemplatedPolicy {
|
||||||
|
tp2 := *tp
|
||||||
|
|
||||||
|
tp2.TemplateVariables = nil
|
||||||
|
if tp.TemplateVariables != nil {
|
||||||
|
tp2.TemplateVariables = tp.TemplateVariables.Clone()
|
||||||
|
}
|
||||||
|
tp2.Datacenters = stringslice.CloneStringSlice(tp.Datacenters)
|
||||||
|
|
||||||
|
return &tp2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *ACLTemplatedPolicy) AddToHash(h hash.Hash) {
|
||||||
|
h.Write([]byte(tp.TemplateID))
|
||||||
|
h.Write([]byte(tp.TemplateName))
|
||||||
|
|
||||||
|
if tp.TemplateVariables != nil {
|
||||||
|
tp.TemplateVariables.AddToHash(h)
|
||||||
|
}
|
||||||
|
for _, dc := range tp.Datacenters {
|
||||||
|
h.Write([]byte(dc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *ACLTemplatedPolicyVariables) AddToHash(h hash.Hash) {
|
||||||
|
h.Write([]byte(tv.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *ACLTemplatedPolicyVariables) Clone() *ACLTemplatedPolicyVariables {
|
||||||
|
tv2 := *tv
|
||||||
|
return &tv2
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates templated policy variables against schema.
|
||||||
|
func (tp *ACLTemplatedPolicy) ValidateTemplatedPolicy(schema string) error {
|
||||||
|
if schema == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
loader := gojsonschema.NewStringLoader(schema)
|
||||||
|
dataloader := gojsonschema.NewGoLoader(tp.TemplateVariables)
|
||||||
|
res, err := gojsonschema.Validate(loader, dataloader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load json schema for validation %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Valid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var merr *multierror.Error
|
||||||
|
|
||||||
|
for _, resultError := range res.Errors() {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf(resultError.Description()))
|
||||||
|
}
|
||||||
|
return merr.ErrorOrNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *ACLTemplatedPolicy) EstimateSize() int {
|
||||||
|
size := len(tp.TemplateName) + len(tp.TemplateID) + tp.TemplateVariables.EstimateSize()
|
||||||
|
for _, dc := range tp.Datacenters {
|
||||||
|
size += len(dc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tv *ACLTemplatedPolicyVariables) EstimateSize() int {
|
||||||
|
return len(tv.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyntheticPolicy generates a policy based on templated policies' ID and variables
|
||||||
|
//
|
||||||
|
// Given that we validate this string name before persisting, we do not
|
||||||
|
// have to escape it before doing the following interpolation.
|
||||||
|
func (tp *ACLTemplatedPolicy) SyntheticPolicy(entMeta *acl.EnterpriseMeta) (*ACLPolicy, error) {
|
||||||
|
rules, err := tp.aclTemplatedPolicyRules(entMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hasher := fnv.New128a()
|
||||||
|
hashID := fmt.Sprintf("%x", hasher.Sum([]byte(rules)))
|
||||||
|
|
||||||
|
policy := &ACLPolicy{
|
||||||
|
Rules: rules,
|
||||||
|
ID: hashID,
|
||||||
|
Name: fmt.Sprintf("synthetic-policy-%s", hashID),
|
||||||
|
Datacenters: tp.Datacenters,
|
||||||
|
Description: fmt.Sprintf("synthetic policy generated from templated policy: %s", tp.TemplateName),
|
||||||
|
}
|
||||||
|
policy.EnterpriseMeta.Merge(entMeta)
|
||||||
|
policy.SetHash(true)
|
||||||
|
|
||||||
|
return policy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *ACLTemplatedPolicy) aclTemplatedPolicyRules(entMeta *acl.EnterpriseMeta) (string, error) {
|
||||||
|
if entMeta == nil {
|
||||||
|
entMeta = DefaultEnterpriseMetaInDefaultPartition()
|
||||||
|
}
|
||||||
|
entMeta.Normalize()
|
||||||
|
|
||||||
|
tpl := template.New(tp.TemplateName)
|
||||||
|
tmplCode, ok := aclTemplatedPoliciesList[tp.TemplateName]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("acl templated policy does not exist: %s", tp.TemplateName)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTpl, err := tpl.Parse(tmplCode.Template)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("an error occured when parsing template structs: %w", err)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = parsedTpl.Execute(&buf, struct {
|
||||||
|
*ACLTemplatedPolicyVariables
|
||||||
|
Namespace string
|
||||||
|
Partition string
|
||||||
|
}{
|
||||||
|
Namespace: entMeta.NamespaceOrDefault(),
|
||||||
|
Partition: entMeta.PartitionOrDefault(),
|
||||||
|
ACLTemplatedPolicyVariables: tp.TemplateVariables,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("an error occured when executing on templated policy variables: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deduplicate returns a new list of templated policies without duplicates.
|
||||||
|
// compares values of template variables to ensure no duplicates
|
||||||
|
func (tps ACLTemplatedPolicies) Deduplicate() ACLTemplatedPolicies {
|
||||||
|
list := make(map[string][]ACLTemplatedPolicyVariables)
|
||||||
|
var out ACLTemplatedPolicies
|
||||||
|
|
||||||
|
for _, tp := range tps {
|
||||||
|
// checks if template name already in the unique list
|
||||||
|
_, found := list[tp.TemplateName]
|
||||||
|
if !found {
|
||||||
|
list[tp.TemplateName] = make([]ACLTemplatedPolicyVariables, 0)
|
||||||
|
}
|
||||||
|
templateSchema := aclTemplatedPoliciesList[tp.TemplateName].Schema
|
||||||
|
|
||||||
|
// if schema is empty, template does not require variables
|
||||||
|
if templateSchema == "" {
|
||||||
|
if !found {
|
||||||
|
out = append(out, tp)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slices.Contains(list[tp.TemplateName], *tp.TemplateVariables) {
|
||||||
|
list[tp.TemplateName] = append(list[tp.TemplateName], *tp.TemplateVariables)
|
||||||
|
out = append(out, tp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetACLTemplatedPolicyBase(templateName string) (*ACLTemplatedPolicyBase, bool) {
|
||||||
|
baseTemplate, found := aclTemplatedPoliciesList[templateName]
|
||||||
|
|
||||||
|
return baseTemplate, found
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
//go:build !consulent
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
const (
|
||||||
|
ACLTemplatedPolicyService = `
|
||||||
|
service "{{.Name}}" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "{{.Name}}-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}`
|
||||||
|
|
||||||
|
ACLTemplatedPolicyNode = `
|
||||||
|
node "{{.Name}}" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}`
|
||||||
|
|
||||||
|
ACLTemplatedPolicyDNS = `
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
query_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *ACLToken) TemplatedPolicyList() []*ACLTemplatedPolicy {
|
||||||
|
if len(t.TemplatedPolicies) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]*ACLTemplatedPolicy, 0, len(t.TemplatedPolicies))
|
||||||
|
for _, n := range t.TemplatedPolicies {
|
||||||
|
out = append(out, n.Clone())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *ACLRole) TemplatedPolicyList() []*ACLTemplatedPolicy {
|
||||||
|
if len(t.TemplatedPolicies) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]*ACLTemplatedPolicy, 0, len(t.TemplatedPolicies))
|
||||||
|
for _, n := range t.TemplatedPolicies {
|
||||||
|
out = append(out, n.Clone())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
//go:build !consulent
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStructs_ACLTemplatedPolicy_SyntheticPolicy(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
templatedPolicy *ACLTemplatedPolicy
|
||||||
|
expectedPolicy *ACLPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]testCase{
|
||||||
|
"service-identity-template": {
|
||||||
|
templatedPolicy: &ACLTemplatedPolicy{
|
||||||
|
TemplateID: ACLTemplatedPolicyServiceID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPolicy: &ACLPolicy{
|
||||||
|
Description: "synthetic policy generated from templated policy: builtin/service",
|
||||||
|
Rules: `
|
||||||
|
service "api" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "api-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"node-identity-template": {
|
||||||
|
templatedPolicy: &ACLTemplatedPolicy{
|
||||||
|
TemplateID: ACLTemplatedPolicyNodeID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPolicy: &ACLPolicy{
|
||||||
|
Description: "synthetic policy generated from templated policy: builtin/node",
|
||||||
|
Rules: `
|
||||||
|
node "web" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"dns-template": {
|
||||||
|
templatedPolicy: &ACLTemplatedPolicy{
|
||||||
|
TemplateID: ACLTemplatedPolicyDNSID,
|
||||||
|
TemplateName: api.ACLTemplatedPolicyDNSName,
|
||||||
|
},
|
||||||
|
expectedPolicy: &ACLPolicy{
|
||||||
|
Description: "synthetic policy generated from templated policy: builtin/dns",
|
||||||
|
Rules: `
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
query_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tcase := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
policy, err := tcase.templatedPolicy.SyntheticPolicy(nil)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tcase.expectedPolicy.Description, policy.Description)
|
||||||
|
require.Equal(t, tcase.expectedPolicy.Rules, policy.Rules)
|
||||||
|
require.Contains(t, policy.Name, "synthetic-policy-")
|
||||||
|
require.NotEmpty(t, policy.Hash)
|
||||||
|
require.NotEmpty(t, policy.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright (c) HashiCorp, Inc.
|
||||||
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
|
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeduplicate(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
templatedPolicies ACLTemplatedPolicies
|
||||||
|
expectedCount int
|
||||||
|
}
|
||||||
|
tcases := map[string]testCase{
|
||||||
|
"multiple-of-the-same-template": {
|
||||||
|
templatedPolicies: ACLTemplatedPolicies{
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 1,
|
||||||
|
},
|
||||||
|
"separate-templates-with-matching-variables": {
|
||||||
|
templatedPolicies: ACLTemplatedPolicies{
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 2,
|
||||||
|
},
|
||||||
|
"separate-templates-with-multiple-matching-variables": {
|
||||||
|
templatedPolicies: ACLTemplatedPolicies{
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyDNSName,
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&ACLTemplatedPolicy{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyDNSName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedCount: 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tcase := range tcases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
policies := tcase.templatedPolicies.Deduplicate()
|
||||||
|
|
||||||
|
require.Equal(t, tcase.expectedCount, len(policies))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
23
api/acl.go
23
api/acl.go
|
@ -19,6 +19,11 @@ const (
|
||||||
|
|
||||||
// ACLManagementType is the management type token
|
// ACLManagementType is the management type token
|
||||||
ACLManagementType = "management"
|
ACLManagementType = "management"
|
||||||
|
|
||||||
|
// ACLTemplatedPolicy names
|
||||||
|
ACLTemplatedPolicyServiceName = "builtin/service"
|
||||||
|
ACLTemplatedPolicyNodeName = "builtin/node"
|
||||||
|
ACLTemplatedPolicyDNSName = "builtin/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ACLLink struct {
|
type ACLLink struct {
|
||||||
|
@ -40,6 +45,7 @@ type ACLToken struct {
|
||||||
Roles []*ACLTokenRoleLink `json:",omitempty"`
|
Roles []*ACLTokenRoleLink `json:",omitempty"`
|
||||||
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
||||||
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
||||||
|
TemplatedPolicies []*ACLTemplatedPolicy `json:",omitempty"`
|
||||||
Local bool
|
Local bool
|
||||||
AuthMethod string `json:",omitempty"`
|
AuthMethod string `json:",omitempty"`
|
||||||
ExpirationTTL time.Duration `json:",omitempty"`
|
ExpirationTTL time.Duration `json:",omitempty"`
|
||||||
|
@ -88,6 +94,7 @@ type ACLTokenListEntry struct {
|
||||||
Roles []*ACLTokenRoleLink `json:",omitempty"`
|
Roles []*ACLTokenRoleLink `json:",omitempty"`
|
||||||
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
||||||
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
||||||
|
TemplatedPolicies []*ACLTemplatedPolicy `json:",omitempty"`
|
||||||
Local bool
|
Local bool
|
||||||
AuthMethod string `json:",omitempty"`
|
AuthMethod string `json:",omitempty"`
|
||||||
ExpirationTime *time.Time `json:",omitempty"`
|
ExpirationTime *time.Time `json:",omitempty"`
|
||||||
|
@ -148,6 +155,21 @@ type ACLNodeIdentity struct {
|
||||||
Datacenter string
|
Datacenter string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACLTemplatedPolicy represents a template used to generate a `synthetic` policy
|
||||||
|
// given some input variables.
|
||||||
|
type ACLTemplatedPolicy struct {
|
||||||
|
TemplateName string
|
||||||
|
TemplateVariables *ACLTemplatedPolicyVariables `json:",omitempty"`
|
||||||
|
|
||||||
|
// Datacenters are an artifact of Nodeidentity & ServiceIdentity.
|
||||||
|
// It is used to facilitate the future migration away from both
|
||||||
|
Datacenters []string `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACLTemplatedPolicyVariables struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
// ACLPolicy represents an ACL Policy.
|
// ACLPolicy represents an ACL Policy.
|
||||||
type ACLPolicy struct {
|
type ACLPolicy struct {
|
||||||
ID string
|
ID string
|
||||||
|
@ -196,6 +218,7 @@ type ACLRole struct {
|
||||||
Policies []*ACLRolePolicyLink `json:",omitempty"`
|
Policies []*ACLRolePolicyLink `json:",omitempty"`
|
||||||
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
|
||||||
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
NodeIdentities []*ACLNodeIdentity `json:",omitempty"`
|
||||||
|
TemplatedPolicies []*ACLTemplatedPolicy `json:",omitempty"`
|
||||||
Hash []byte
|
Hash []byte
|
||||||
CreateIndex uint64
|
CreateIndex uint64
|
||||||
ModifyIndex uint64
|
ModifyIndex uint64
|
||||||
|
|
|
@ -10,6 +10,9 @@ import (
|
||||||
"github.com/hashicorp/consul/acl"
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/hashicorp/consul/command/helpers"
|
||||||
|
"github.com/hashicorp/hcl"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTokenAccessorIDFromPartial(client *api.Client, partialAccessorID string) (string, error) {
|
func GetTokenAccessorIDFromPartial(client *api.Client, partialAccessorID string) (string, error) {
|
||||||
|
@ -218,6 +221,70 @@ func ExtractNodeIdentities(nodeIdents []string) ([]*api.ACLNodeIdentity, error)
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExtractTemplatedPolicies(templatedPolicy string, templatedPolicyFile string, templatedPolicyVariables []string) ([]*api.ACLTemplatedPolicy, error) {
|
||||||
|
var out []*api.ACLTemplatedPolicy
|
||||||
|
if templatedPolicy == "" && templatedPolicyFile == "" {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if templatedPolicy != "" {
|
||||||
|
parsedVariables, err := getTemplatedPolicyVariables(templatedPolicyVariables)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, &api.ACLTemplatedPolicy{
|
||||||
|
TemplateName: templatedPolicy,
|
||||||
|
TemplateVariables: parsedVariables,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if templatedPolicyFile != "" {
|
||||||
|
fileData, err := helpers.LoadFromFile(templatedPolicyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config map[string]map[string][]api.ACLTemplatedPolicyVariables
|
||||||
|
err = hcl.Decode(&config, fileData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for templateName, templateVariables := range config["TemplatedPolicy"] {
|
||||||
|
for _, tp := range templateVariables {
|
||||||
|
out = append(out, &api.ACLTemplatedPolicy{
|
||||||
|
TemplateName: templateName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: tp.Name,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTemplatedPolicyVariables(variables []string) (*api.ACLTemplatedPolicyVariables, error) {
|
||||||
|
if len(variables) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &api.ACLTemplatedPolicyVariables{}
|
||||||
|
jsonVariables := make(map[string]string)
|
||||||
|
|
||||||
|
for _, variable := range variables {
|
||||||
|
parts := strings.Split(variable, ":")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("malformed -var argument: %q, expecting VariableName:Value", variable)
|
||||||
|
}
|
||||||
|
jsonVariables[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mapstructure.Decode(jsonVariables, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
// TestKubernetesJWT_A is a valid service account jwt extracted from a minikube setup.
|
// TestKubernetesJWT_A is a valid service account jwt extracted from a minikube setup.
|
||||||
//
|
//
|
||||||
// {
|
// {
|
||||||
|
|
|
@ -71,7 +71,7 @@ func (c *cmd) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.name == "" {
|
if c.name == "" {
|
||||||
c.UI.Error(fmt.Sprintf("Missing require '-name' flag"))
|
c.UI.Error(fmt.Sprintf("Missing required '-name' flag"))
|
||||||
c.UI.Error(c.Help())
|
c.UI.Error(c.Help())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,15 @@ type cmd struct {
|
||||||
http *flags.HTTPFlags
|
http *flags.HTTPFlags
|
||||||
help string
|
help string
|
||||||
|
|
||||||
name string
|
name string
|
||||||
description string
|
description string
|
||||||
policyIDs []string
|
policyIDs []string
|
||||||
policyNames []string
|
policyNames []string
|
||||||
serviceIdents []string
|
serviceIdents []string
|
||||||
nodeIdents []string
|
nodeIdents []string
|
||||||
|
templatedPolicy string
|
||||||
|
templatedPolicyFile string
|
||||||
|
templatedPolicyVariables []string
|
||||||
|
|
||||||
showMeta bool
|
showMeta bool
|
||||||
format string
|
format string
|
||||||
|
@ -55,6 +58,12 @@ func (c *cmd) init() {
|
||||||
c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+
|
c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+
|
||||||
"node identity to use for this role. May be specified multiple times. Format is "+
|
"node identity to use for this role. May be specified multiple times. Format is "+
|
||||||
"NODENAME:DATACENTER")
|
"NODENAME:DATACENTER")
|
||||||
|
c.flags.Var((*flags.AppendSliceValue)(&c.templatedPolicyVariables), "var", "Templated policy variables."+
|
||||||
|
" Must be used in combination with -templated-policy flag to specify required variables."+
|
||||||
|
" May be specified multiple times with different variables."+
|
||||||
|
" Format is VariableName:Value")
|
||||||
|
c.flags.StringVar(&c.templatedPolicy, "templated-policy", "", "The templated policy name. Use -var flag to specify variables when required.")
|
||||||
|
c.flags.StringVar(&c.templatedPolicyFile, "templated-policy-file", "", "Path to a file containing templated policy names and variables.")
|
||||||
c.flags.StringVar(
|
c.flags.StringVar(
|
||||||
&c.format,
|
&c.format,
|
||||||
"format",
|
"format",
|
||||||
|
@ -74,13 +83,14 @@ func (c *cmd) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.name == "" {
|
if c.name == "" {
|
||||||
c.UI.Error(fmt.Sprintf("Missing require '-name' flag"))
|
c.UI.Error("Missing required '-name' flag")
|
||||||
c.UI.Error(c.Help())
|
c.UI.Error(c.Help())
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.policyNames) == 0 && len(c.policyIDs) == 0 && len(c.serviceIdents) == 0 && len(c.nodeIdents) == 0 {
|
if len(c.policyNames) == 0 && len(c.policyIDs) == 0 && len(c.serviceIdents) == 0 && len(c.nodeIdents) == 0 &&
|
||||||
c.UI.Error(fmt.Sprintf("Cannot create a role without specifying -policy-name, -policy-id, -service-identity, or -node-identity at least once"))
|
len(c.templatedPolicy) == 0 && len(c.templatedPolicyFile) == 0 {
|
||||||
|
c.UI.Error("Cannot create a role without specifying -policy-name, -policy-id, -service-identity, -node-identity, -templated-policy-file or -templated-policy at least once")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,6 +134,13 @@ func (c *cmd) Run(args []string) int {
|
||||||
}
|
}
|
||||||
newRole.NodeIdentities = parsedNodeIdents
|
newRole.NodeIdentities = parsedNodeIdents
|
||||||
|
|
||||||
|
parsedTemplatedPolicies, err := acl.ExtractTemplatedPolicies(c.templatedPolicy, c.templatedPolicyFile, c.templatedPolicyVariables)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
newRole.TemplatedPolicies = parsedTemplatedPolicies
|
||||||
|
|
||||||
r, _, err := client.ACL().RoleCreate(newRole, nil)
|
r, _, err := client.ACL().RoleCreate(newRole, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Failed to create new role: %v", err))
|
c.UI.Error(fmt.Sprintf("Failed to create new role: %v", err))
|
||||||
|
@ -167,5 +184,7 @@ Usage: consul acl role create -name NAME [options]
|
||||||
-policy-id b52fc3de-5 \
|
-policy-id b52fc3de-5 \
|
||||||
-policy-name "acl-replication" \
|
-policy-name "acl-replication" \
|
||||||
-service-identity "web" \
|
-service-identity "web" \
|
||||||
-service-identity "db:east,west"
|
-service-identity "db:east,west" \
|
||||||
|
-templated-policy "builtin/service" \
|
||||||
|
-var "name:api"
|
||||||
`
|
`
|
||||||
|
|
|
@ -89,6 +89,20 @@ func (f *prettyFormatter) FormatRole(role *api.ACLRole) (string, error) {
|
||||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
|
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(role.TemplatedPolicies) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintln("Templated Policies:"))
|
||||||
|
for _, templatedPolicy := range role.TemplatedPolicies {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" %s\n", templatedPolicy.TemplateName))
|
||||||
|
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Name: %s\n", templatedPolicy.TemplateVariables.Name))
|
||||||
|
}
|
||||||
|
if len(templatedPolicy.Datacenters) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(" Datacenters: all\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
@ -144,6 +158,21 @@ func (f *prettyFormatter) formatRoleListEntry(role *api.ACLRole) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(role.TemplatedPolicies) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintln(" Templated Policies:"))
|
||||||
|
for _, templatedPolicy := range role.TemplatedPolicies {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" %s\n", templatedPolicy.TemplateName))
|
||||||
|
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Name: %s\n", templatedPolicy.TemplateVariables.Name))
|
||||||
|
}
|
||||||
|
if len(templatedPolicy.Datacenters) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(" Datacenters: all\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,14 @@ func TestFormatRole(t *testing.T) {
|
||||||
Datacenter: "middleearth-northwest",
|
Datacenter: "middleearth-northwest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "gardener"},
|
||||||
|
Datacenters: []string{"middleearth-northwest", "somewhere-east"},
|
||||||
|
},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "bagend"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -114,7 +122,7 @@ func TestFormatRole(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatTokenList(t *testing.T) {
|
func TestFormatRoleList(t *testing.T) {
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
roles []*api.ACLRole
|
roles []*api.ACLRole
|
||||||
overrideGoldenName string
|
overrideGoldenName string
|
||||||
|
@ -165,6 +173,10 @@ func TestFormatTokenList(t *testing.T) {
|
||||||
Datacenter: "middleearth-northwest",
|
Datacenter: "middleearth-northwest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "gardener"}},
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "bagend"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,6 +26,24 @@
|
||||||
"Datacenter": "middleearth-northwest"
|
"Datacenter": "middleearth-northwest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"TemplatedPolicies": [
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/service",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "gardener"
|
||||||
|
},
|
||||||
|
"Datacenters": [
|
||||||
|
"middleearth-northwest",
|
||||||
|
"somewhere-east"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/node",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "bagend"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"Hash": "YWJjZGVmZ2g=",
|
"Hash": "YWJjZGVmZ2g=",
|
||||||
"CreateIndex": 5,
|
"CreateIndex": 5,
|
||||||
"ModifyIndex": 10,
|
"ModifyIndex": 10,
|
||||||
|
|
|
@ -12,3 +12,10 @@ Service Identities:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: gardener
|
||||||
|
Datacenters: middleearth-northwest, somewhere-east
|
||||||
|
builtin/node
|
||||||
|
Name: bagend
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -9,3 +9,10 @@ Service Identities:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: gardener
|
||||||
|
Datacenters: middleearth-northwest, somewhere-east
|
||||||
|
builtin/node
|
||||||
|
Name: bagend
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -27,6 +27,20 @@
|
||||||
"Datacenter": "middleearth-northwest"
|
"Datacenter": "middleearth-northwest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"TemplatedPolicies": [
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/service",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "gardener"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/node",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "bagend"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"Hash": "YWJjZGVmZ2g=",
|
"Hash": "YWJjZGVmZ2g=",
|
||||||
"CreateIndex": 5,
|
"CreateIndex": 5,
|
||||||
"ModifyIndex": 10,
|
"ModifyIndex": 10,
|
||||||
|
|
|
@ -12,3 +12,10 @@ complex:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: gardener
|
||||||
|
Datacenters: all
|
||||||
|
builtin/node
|
||||||
|
Name: bagend
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -9,3 +9,10 @@ complex:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: gardener
|
||||||
|
Datacenters: all
|
||||||
|
builtin/node
|
||||||
|
Name: bagend
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -28,13 +28,18 @@ type cmd struct {
|
||||||
http *flags.HTTPFlags
|
http *flags.HTTPFlags
|
||||||
help string
|
help string
|
||||||
|
|
||||||
roleID string
|
roleID string
|
||||||
name string
|
name string
|
||||||
description string
|
description string
|
||||||
policyIDs []string
|
policyIDs []string
|
||||||
policyNames []string
|
policyNames []string
|
||||||
serviceIdents []string
|
serviceIdents []string
|
||||||
nodeIdents []string
|
nodeIdents []string
|
||||||
|
appendTemplatedPolicy string
|
||||||
|
replaceTemplatedPolicy string
|
||||||
|
appendTemplatedPolicyFile string
|
||||||
|
replaceTemplatedPolicyFile string
|
||||||
|
templatedPolicyVariables []string
|
||||||
|
|
||||||
noMerge bool
|
noMerge bool
|
||||||
showMeta bool
|
showMeta bool
|
||||||
|
@ -69,6 +74,16 @@ func (c *cmd) init() {
|
||||||
role.PrettyFormat,
|
role.PrettyFormat,
|
||||||
fmt.Sprintf("Output format {%s}", strings.Join(role.GetSupportedFormats(), "|")),
|
fmt.Sprintf("Output format {%s}", strings.Join(role.GetSupportedFormats(), "|")),
|
||||||
)
|
)
|
||||||
|
c.flags.Var((*flags.AppendSliceValue)(&c.templatedPolicyVariables), "var", "Templated policy variables."+
|
||||||
|
" Must be used in combination with -append-templated-policy or -replace-templated-policy flags to specify required variables."+
|
||||||
|
" May be specified multiple times with different variables."+
|
||||||
|
" Format is VariableName:Value")
|
||||||
|
c.flags.StringVar(&c.appendTemplatedPolicy, "append-templated-policy", "", "The templated policy name to attach to the role's existing templated policies list. Use -var flag to specify variables when required."+
|
||||||
|
" The role retains existing templated policies.")
|
||||||
|
c.flags.StringVar(&c.replaceTemplatedPolicy, "replace-templated-policy", "", "The templated policy name to replace the existing templated policies list with. Use -var flag to specify variables when required."+
|
||||||
|
" Overwrites the role's existing templated policies.")
|
||||||
|
c.flags.StringVar(&c.appendTemplatedPolicyFile, "append-templated-policy-file", "", "Path to a file containing templated policies and variables. Works like `-append-templated-policy`. The role retains existing templated policies.")
|
||||||
|
c.flags.StringVar(&c.replaceTemplatedPolicyFile, "replace-templated-policy-file", "", "Path to a file containing templated policies and variables. Works like `-replace-templated-policy`. Overwrites the role's existing templated policies.")
|
||||||
|
|
||||||
c.http = &flags.HTTPFlags{}
|
c.http = &flags.HTTPFlags{}
|
||||||
flags.Merge(c.flags, c.http.ClientFlags())
|
flags.Merge(c.flags, c.http.ClientFlags())
|
||||||
|
@ -111,6 +126,24 @@ func (c *cmd) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAppendTemplatedPolicies := len(c.appendTemplatedPolicy) > 0 || len(c.appendTemplatedPolicyFile) > 0
|
||||||
|
hasReplaceTemplatedPolicies := len(c.replaceTemplatedPolicy) > 0 || len(c.replaceTemplatedPolicyFile) > 0
|
||||||
|
|
||||||
|
if hasReplaceTemplatedPolicies && hasAppendTemplatedPolicies {
|
||||||
|
c.UI.Error("Cannot combine the use of append-templated-policy flags with replace-templated-policy. " +
|
||||||
|
"To set or overwrite existing templated policies, use -replace-templated-policy or -replace-templated-policy-file. " +
|
||||||
|
"To append to existing templated policies, use -append-templated-policy or -append-templated-policy-file.")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
parsedTemplatedPolicies, err := acl.ExtractTemplatedPolicies(c.replaceTemplatedPolicy, c.replaceTemplatedPolicyFile, c.templatedPolicyVariables)
|
||||||
|
if hasAppendTemplatedPolicies {
|
||||||
|
parsedTemplatedPolicies, err = acl.ExtractTemplatedPolicies(c.appendTemplatedPolicy, c.appendTemplatedPolicyFile, c.templatedPolicyVariables)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
// Read the current role in both cases so we can fail better if not found.
|
// Read the current role in both cases so we can fail better if not found.
|
||||||
currentRole, _, err := client.ACL().RoleRead(roleID, nil)
|
currentRole, _, err := client.ACL().RoleRead(roleID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -129,6 +162,7 @@ func (c *cmd) Run(args []string) int {
|
||||||
Description: c.description,
|
Description: c.description,
|
||||||
ServiceIdentities: parsedServiceIdents,
|
ServiceIdentities: parsedServiceIdents,
|
||||||
NodeIdentities: parsedNodeIdents,
|
NodeIdentities: parsedNodeIdents,
|
||||||
|
TemplatedPolicies: parsedTemplatedPolicies,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, policyName := range c.policyNames {
|
for _, policyName := range c.policyNames {
|
||||||
|
@ -221,6 +255,12 @@ func (c *cmd) Run(args []string) int {
|
||||||
r.NodeIdentities = append(r.NodeIdentities, nodeid)
|
r.NodeIdentities = append(r.NodeIdentities, nodeid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasReplaceTemplatedPolicies {
|
||||||
|
r.TemplatedPolicies = parsedTemplatedPolicies
|
||||||
|
} else {
|
||||||
|
r.TemplatedPolicies = append(r.TemplatedPolicies, parsedTemplatedPolicies...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r, _, err = client.ACL().RoleUpdate(r, nil)
|
r, _, err = client.ACL().RoleUpdate(r, nil)
|
||||||
|
@ -273,6 +313,8 @@ Usage: consul acl role update [options]
|
||||||
-name "better-name" \
|
-name "better-name" \
|
||||||
-description "replication" \
|
-description "replication" \
|
||||||
-policy-name "token-replication" \
|
-policy-name "token-replication" \
|
||||||
-service-identity "web"
|
-service-identity "web" \
|
||||||
|
-templated-policy "builtin/service" \
|
||||||
|
-var "name:api"
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
|
@ -68,6 +68,9 @@ func TestRoleUpdateCommand(t *testing.T) {
|
||||||
ServiceName: "fake",
|
ServiceName: "fake",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "fake"}},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
&api.WriteOptions{Token: "root"},
|
&api.WriteOptions{Token: "root"},
|
||||||
)
|
)
|
||||||
|
@ -122,6 +125,7 @@ func TestRoleUpdateCommand(t *testing.T) {
|
||||||
require.Equal(t, "test role edited", role.Description)
|
require.Equal(t, "test role edited", role.Description)
|
||||||
require.Len(t, role.Policies, 1)
|
require.Len(t, role.Policies, 1)
|
||||||
require.Len(t, role.ServiceIdentities, 1)
|
require.Len(t, role.ServiceIdentities, 1)
|
||||||
|
require.Len(t, role.TemplatedPolicies, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("update with policy by id", func(t *testing.T) {
|
t.Run("update with policy by id", func(t *testing.T) {
|
||||||
|
@ -198,6 +202,47 @@ func TestRoleUpdateCommand(t *testing.T) {
|
||||||
require.Len(t, role.ServiceIdentities, 3)
|
require.Len(t, role.ServiceIdentities, 3)
|
||||||
require.Len(t, role.NodeIdentities, 1)
|
require.Len(t, role.NodeIdentities, 1)
|
||||||
})
|
})
|
||||||
|
t.Run("update with append templated policies", func(t *testing.T) {
|
||||||
|
_ = run(t, []string{
|
||||||
|
"-id=" + role.ID,
|
||||||
|
"-token=root",
|
||||||
|
"-append-templated-policy=builtin/service",
|
||||||
|
"-var=name:api",
|
||||||
|
})
|
||||||
|
|
||||||
|
role, _, err := client.ACL().RoleRead(
|
||||||
|
role.ID,
|
||||||
|
&api.QueryOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, role)
|
||||||
|
require.Equal(t, "test role edited", role.Description)
|
||||||
|
require.Len(t, role.Policies, 2)
|
||||||
|
require.Len(t, role.ServiceIdentities, 3)
|
||||||
|
require.Len(t, role.NodeIdentities, 1)
|
||||||
|
require.Len(t, role.TemplatedPolicies, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update with replace templated policies", func(t *testing.T) {
|
||||||
|
_ = run(t, []string{
|
||||||
|
"-id=" + role.ID,
|
||||||
|
"-token=root",
|
||||||
|
"-replace-templated-policy=builtin/service",
|
||||||
|
"-var=name:api",
|
||||||
|
})
|
||||||
|
|
||||||
|
role, _, err := client.ACL().RoleRead(
|
||||||
|
role.ID,
|
||||||
|
&api.QueryOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, role)
|
||||||
|
require.Equal(t, "test role edited", role.Description)
|
||||||
|
require.Len(t, role.Policies, 2)
|
||||||
|
require.Len(t, role.ServiceIdentities, 3)
|
||||||
|
require.Len(t, role.NodeIdentities, 1)
|
||||||
|
require.Len(t, role.TemplatedPolicies, 1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoleUpdateCommand_JSON(t *testing.T) {
|
func TestRoleUpdateCommand_JSON(t *testing.T) {
|
||||||
|
@ -335,6 +380,9 @@ func TestRoleUpdateCommand_noMerge(t *testing.T) {
|
||||||
ServiceName: "fake",
|
ServiceName: "fake",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "fake"}},
|
||||||
|
},
|
||||||
Policies: []*api.ACLRolePolicyLink{
|
Policies: []*api.ACLRolePolicyLink{
|
||||||
{
|
{
|
||||||
ID: policy3.ID,
|
ID: policy3.ID,
|
||||||
|
@ -482,4 +530,64 @@ func TestRoleUpdateCommand_noMerge(t *testing.T) {
|
||||||
require.Len(t, role.Policies, 0)
|
require.Len(t, role.Policies, 0)
|
||||||
require.Len(t, role.ServiceIdentities, 1)
|
require.Len(t, role.ServiceIdentities, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("update with templated policy append", func(t *testing.T) {
|
||||||
|
role := createRole(t)
|
||||||
|
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := New(ui)
|
||||||
|
args := []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-id=" + role.ID,
|
||||||
|
"-name=" + role.Name,
|
||||||
|
"-token=root",
|
||||||
|
"-no-merge",
|
||||||
|
"-append-templated-policy=builtin/service",
|
||||||
|
"-var=name:api",
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cmd.Run(args)
|
||||||
|
require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String())
|
||||||
|
require.Empty(t, ui.ErrorWriter.String())
|
||||||
|
|
||||||
|
role, _, err := client.ACL().RoleRead(
|
||||||
|
role.ID,
|
||||||
|
&api.QueryOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, role)
|
||||||
|
require.Equal(t, "", role.Description)
|
||||||
|
require.Len(t, role.Policies, 0)
|
||||||
|
require.Len(t, role.TemplatedPolicies, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("update with replace templated policy", func(t *testing.T) {
|
||||||
|
role := createRole(t)
|
||||||
|
|
||||||
|
ui := cli.NewMockUi()
|
||||||
|
cmd := New(ui)
|
||||||
|
args := []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-id=" + role.ID,
|
||||||
|
"-name=" + role.Name,
|
||||||
|
"-token=root",
|
||||||
|
"-no-merge",
|
||||||
|
"-replace-templated-policy=builtin/service",
|
||||||
|
"-var=name:api",
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cmd.Run(args)
|
||||||
|
require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String())
|
||||||
|
require.Empty(t, ui.ErrorWriter.String())
|
||||||
|
|
||||||
|
role, _, err := client.ACL().RoleRead(
|
||||||
|
role.ID,
|
||||||
|
&api.QueryOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, role)
|
||||||
|
require.Equal(t, "", role.Description)
|
||||||
|
require.Len(t, role.Policies, 0)
|
||||||
|
require.Len(t, role.TemplatedPolicies, 1)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,7 @@ func TestTokenCloneCommand_Pretty(t *testing.T) {
|
||||||
require.Equal(t, cloned.Description, apiToken.Description)
|
require.Equal(t, cloned.Description, apiToken.Description)
|
||||||
require.Equal(t, cloned.Local, apiToken.Local)
|
require.Equal(t, cloned.Local, apiToken.Local)
|
||||||
require.Equal(t, cloned.Policies, apiToken.Policies)
|
require.Equal(t, cloned.Policies, apiToken.Policies)
|
||||||
|
require.Equal(t, cloned.TemplatedPolicies, apiToken.TemplatedPolicies)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +199,13 @@ func TestTokenCloneCommand_JSON(t *testing.T) {
|
||||||
|
|
||||||
// create a token
|
// create a token
|
||||||
token, _, err := client.ACL().TokenCreate(
|
token, _, err := client.ACL().TokenCreate(
|
||||||
&api.ACLToken{Description: "test", Policies: []*api.ACLTokenPolicyLink{{Name: "test-policy"}}},
|
&api.ACLToken{
|
||||||
|
Description: "test",
|
||||||
|
Policies: []*api.ACLTokenPolicyLink{{Name: "test-policy"}},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "web"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
&api.WriteOptions{Token: "root"},
|
&api.WriteOptions{Token: "root"},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -29,19 +29,22 @@ type cmd struct {
|
||||||
http *flags.HTTPFlags
|
http *flags.HTTPFlags
|
||||||
help string
|
help string
|
||||||
|
|
||||||
accessor string
|
accessor string
|
||||||
secret string
|
secret string
|
||||||
policyIDs []string
|
policyIDs []string
|
||||||
policyNames []string
|
policyNames []string
|
||||||
description string
|
description string
|
||||||
roleIDs []string
|
roleIDs []string
|
||||||
roleNames []string
|
roleNames []string
|
||||||
serviceIdents []string
|
serviceIdents []string
|
||||||
nodeIdents []string
|
nodeIdents []string
|
||||||
expirationTTL time.Duration
|
templatedPolicy string
|
||||||
local bool
|
templatedPolicyFile string
|
||||||
showMeta bool
|
templatedPolicyVariables []string
|
||||||
format string
|
expirationTTL time.Duration
|
||||||
|
local bool
|
||||||
|
showMeta bool
|
||||||
|
format string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmd) init() {
|
func (c *cmd) init() {
|
||||||
|
@ -76,6 +79,12 @@ func (c *cmd) init() {
|
||||||
token.PrettyFormat,
|
token.PrettyFormat,
|
||||||
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
fmt.Sprintf("Output format {%s}", strings.Join(token.GetSupportedFormats(), "|")),
|
||||||
)
|
)
|
||||||
|
c.flags.Var((*flags.AppendSliceValue)(&c.templatedPolicyVariables), "var", "Templated policy variables."+
|
||||||
|
" Must be used in combination with -templated-policy flag to specify required variables."+
|
||||||
|
" May be specified multiple times with different variables."+
|
||||||
|
" Format is VariableName:Value")
|
||||||
|
c.flags.StringVar(&c.templatedPolicy, "templated-policy", "", "The templated policy name. Use -var flag to specify variables when required.")
|
||||||
|
c.flags.StringVar(&c.templatedPolicyFile, "templated-policy-file", "", "Path to a file containing templated policies and variables.")
|
||||||
c.http = &flags.HTTPFlags{}
|
c.http = &flags.HTTPFlags{}
|
||||||
flags.Merge(c.flags, c.http.ClientFlags())
|
flags.Merge(c.flags, c.http.ClientFlags())
|
||||||
flags.Merge(c.flags, c.http.ServerFlags())
|
flags.Merge(c.flags, c.http.ServerFlags())
|
||||||
|
@ -90,8 +99,9 @@ func (c *cmd) Run(args []string) int {
|
||||||
|
|
||||||
if len(c.policyNames) == 0 && len(c.policyIDs) == 0 &&
|
if len(c.policyNames) == 0 && len(c.policyIDs) == 0 &&
|
||||||
len(c.roleNames) == 0 && len(c.roleIDs) == 0 &&
|
len(c.roleNames) == 0 && len(c.roleIDs) == 0 &&
|
||||||
len(c.serviceIdents) == 0 && len(c.nodeIdents) == 0 {
|
len(c.serviceIdents) == 0 && len(c.nodeIdents) == 0 &&
|
||||||
c.UI.Error(fmt.Sprintf("Cannot create a token without specifying -policy-name, -policy-id, -role-name, -role-id, -service-identity, or -node-identity at least once"))
|
len(c.templatedPolicy) == 0 && len(c.templatedPolicyFile) == 0 {
|
||||||
|
c.UI.Error("Cannot create a token without specifying -policy-name, -policy-id, -role-name, -role-id, -service-identity, -node-identity, -templated-policy, or -templated-policy-file at least once")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +135,13 @@ func (c *cmd) Run(args []string) int {
|
||||||
}
|
}
|
||||||
newToken.NodeIdentities = parsedNodeIdents
|
newToken.NodeIdentities = parsedNodeIdents
|
||||||
|
|
||||||
|
parsedTemplatedPolicies, err := acl.ExtractTemplatedPolicies(c.templatedPolicy, c.templatedPolicyFile, c.templatedPolicyVariables)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
newToken.TemplatedPolicies = parsedTemplatedPolicies
|
||||||
|
|
||||||
for _, policyName := range c.policyNames {
|
for _, policyName := range c.policyNames {
|
||||||
// We could resolve names to IDs here but there isn't any reason why its would be better
|
// We could resolve names to IDs here but there isn't any reason why its would be better
|
||||||
// than allowing the agent to do it.
|
// than allowing the agent to do it.
|
||||||
|
@ -203,6 +220,8 @@ Usage: consul acl token create [options]
|
||||||
-role-id c630d4ef-6 \
|
-role-id c630d4ef-6 \
|
||||||
-role-name "db-updater" \
|
-role-name "db-updater" \
|
||||||
-service-identity "web" \
|
-service-identity "web" \
|
||||||
-service-identity "db:east,west"
|
-service-identity "db:east,west" \
|
||||||
|
-templated-policy "builtin/service" \
|
||||||
|
-var "name:web"
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
|
@ -107,6 +107,27 @@ func TestTokenCreateCommand_Pretty(t *testing.T) {
|
||||||
require.Equal(t, a.Config.NodeName, nodes[0].Node)
|
require.Equal(t, a.Config.NodeName, nodes[0].Node)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// templated policy
|
||||||
|
t.Run("templated-policy", func(t *testing.T) {
|
||||||
|
token := run(t, []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-token=root",
|
||||||
|
"-templated-policy=builtin/node",
|
||||||
|
"-var=name:" + a.Config.NodeName,
|
||||||
|
})
|
||||||
|
|
||||||
|
conf := api.DefaultConfig()
|
||||||
|
conf.Address = a.HTTPAddr()
|
||||||
|
conf.Token = token.SecretID
|
||||||
|
client, err := api.NewClient(conf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nodes, _, err := client.Catalog().Nodes(nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, nodes, 1)
|
||||||
|
require.Equal(t, a.Config.NodeName, nodes[0].Node)
|
||||||
|
})
|
||||||
|
|
||||||
// create with accessor and secret
|
// create with accessor and secret
|
||||||
t.Run("predefined-ids", func(t *testing.T) {
|
t.Run("predefined-ids", func(t *testing.T) {
|
||||||
token := run(t, []string{
|
token := run(t, []string{
|
||||||
|
|
|
@ -109,6 +109,20 @@ func (f *prettyFormatter) FormatToken(token *api.ACLToken) (string, error) {
|
||||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
|
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(token.TemplatedPolicies) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintln("Templated Policies:"))
|
||||||
|
for _, templatedPolicy := range token.TemplatedPolicies {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" %s\n", templatedPolicy.TemplateName))
|
||||||
|
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Name: %s\n", templatedPolicy.TemplateVariables.Name))
|
||||||
|
}
|
||||||
|
if len(templatedPolicy.Datacenters) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(" Datacenters: all\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
@ -174,10 +188,7 @@ func (f *prettyFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (stri
|
||||||
}
|
}
|
||||||
identity := structs.ACLServiceIdentity{ServiceName: svcIdentity.ServiceName, Datacenters: svcIdentity.Datacenters}
|
identity := structs.ACLServiceIdentity{ServiceName: svcIdentity.ServiceName, Datacenters: svcIdentity.Datacenters}
|
||||||
policy := identity.SyntheticPolicy(&entMeta)
|
policy := identity.SyntheticPolicy(&entMeta)
|
||||||
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description))
|
displaySyntheticPolicy(policy, &buffer, indent)
|
||||||
buffer.WriteString(indent + WHITESPACE_2 + "Rules:")
|
|
||||||
buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4))
|
|
||||||
buffer.WriteString("\n\n")
|
|
||||||
}
|
}
|
||||||
if len(token.ACLToken.ServiceIdentities) > 0 {
|
if len(token.ACLToken.ServiceIdentities) > 0 {
|
||||||
buffer.WriteString("Service Identities:\n")
|
buffer.WriteString("Service Identities:\n")
|
||||||
|
@ -190,10 +201,7 @@ func (f *prettyFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (stri
|
||||||
buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenter: %s)\n", nodeIdentity.NodeName, nodeIdentity.Datacenter))
|
buffer.WriteString(fmt.Sprintf(indent+"Name: %s (Datacenter: %s)\n", nodeIdentity.NodeName, nodeIdentity.Datacenter))
|
||||||
identity := structs.ACLNodeIdentity{NodeName: nodeIdentity.NodeName, Datacenter: nodeIdentity.Datacenter}
|
identity := structs.ACLNodeIdentity{NodeName: nodeIdentity.NodeName, Datacenter: nodeIdentity.Datacenter}
|
||||||
policy := identity.SyntheticPolicy(&entMeta)
|
policy := identity.SyntheticPolicy(&entMeta)
|
||||||
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description))
|
displaySyntheticPolicy(policy, &buffer, indent)
|
||||||
buffer.WriteString(indent + WHITESPACE_2 + "Rules:")
|
|
||||||
buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4))
|
|
||||||
buffer.WriteString("\n\n")
|
|
||||||
}
|
}
|
||||||
if len(token.ACLToken.NodeIdentities) > 0 {
|
if len(token.ACLToken.NodeIdentities) > 0 {
|
||||||
buffer.WriteString("Node Identities:\n")
|
buffer.WriteString("Node Identities:\n")
|
||||||
|
@ -202,6 +210,34 @@ func (f *prettyFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatTemplatedPolicy := func(templatedPolicy *api.ACLTemplatedPolicy, indent string) {
|
||||||
|
buffer.WriteString(fmt.Sprintf(indent+"%s\n", templatedPolicy.TemplateName))
|
||||||
|
tp := structs.ACLTemplatedPolicy{
|
||||||
|
TemplateName: templatedPolicy.TemplateName,
|
||||||
|
Datacenters: templatedPolicy.Datacenters,
|
||||||
|
}
|
||||||
|
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
|
||||||
|
tp.TemplateVariables = &structs.ACLTemplatedPolicyVariables{
|
||||||
|
Name: templatedPolicy.TemplateVariables.Name,
|
||||||
|
}
|
||||||
|
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Name: %s\n", templatedPolicy.TemplateVariables.Name))
|
||||||
|
}
|
||||||
|
if len(templatedPolicy.Datacenters) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(fmt.Sprintf(indent + WHITESPACE_2 + "Datacenters: all\n"))
|
||||||
|
}
|
||||||
|
policy, _ := tp.SyntheticPolicy(&entMeta)
|
||||||
|
displaySyntheticPolicy(policy, &buffer, indent)
|
||||||
|
}
|
||||||
|
if len(token.ACLToken.TemplatedPolicies) > 0 {
|
||||||
|
buffer.WriteString("Templated Policies:\n")
|
||||||
|
|
||||||
|
for _, templatedPolicy := range token.ACLToken.TemplatedPolicies {
|
||||||
|
formatTemplatedPolicy(templatedPolicy, WHITESPACE_2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
formatRole := func(role api.ACLRole, indent string) {
|
formatRole := func(role api.ACLRole, indent string) {
|
||||||
buffer.WriteString(fmt.Sprintf(indent+"Role Name: %s\n", role.Name))
|
buffer.WriteString(fmt.Sprintf(indent+"Role Name: %s\n", role.Name))
|
||||||
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"ID: %s\n", role.ID))
|
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"ID: %s\n", role.ID))
|
||||||
|
@ -266,6 +302,13 @@ func (f *prettyFormatter) FormatTokenExpanded(token *api.ACLTokenExpanded) (stri
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func displaySyntheticPolicy(policy *structs.ACLPolicy, buffer *bytes.Buffer, indent string) {
|
||||||
|
buffer.WriteString(fmt.Sprintf(indent+WHITESPACE_2+"Description: %s\n", policy.Description))
|
||||||
|
buffer.WriteString(indent + WHITESPACE_2 + "Rules:")
|
||||||
|
buffer.WriteString(strings.ReplaceAll(policy.Rules, "\n", "\n"+indent+WHITESPACE_4))
|
||||||
|
buffer.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
func (f *prettyFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) {
|
func (f *prettyFormatter) FormatTokenList(tokens []*api.ACLTokenListEntry) (string, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
@ -335,6 +378,21 @@ func (f *prettyFormatter) formatTokenListEntry(token *api.ACLTokenListEntry) str
|
||||||
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
|
buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(token.TemplatedPolicies) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintln("Templated Policies:"))
|
||||||
|
for _, templatedPolicy := range token.TemplatedPolicies {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" %s\n", templatedPolicy.TemplateName))
|
||||||
|
if templatedPolicy.TemplateVariables != nil && templatedPolicy.TemplateVariables.Name != "" {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Name: %s\n", templatedPolicy.TemplateVariables.Name))
|
||||||
|
}
|
||||||
|
if len(templatedPolicy.Datacenters) > 0 {
|
||||||
|
buffer.WriteString(fmt.Sprintf(" Datacenters: %s\n", strings.Join(templatedPolicy.Datacenters, ", ")))
|
||||||
|
} else {
|
||||||
|
buffer.WriteString(" Datacenters: all\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,21 @@ func TestFormatToken(t *testing.T) {
|
||||||
Datacenter: "middleearth-northwest",
|
Datacenter: "middleearth-northwest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"middleearth-northwest", "somewhere-east"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -209,6 +224,21 @@ func TestFormatTokenList(t *testing.T) {
|
||||||
Datacenter: "middleearth-northwest",
|
Datacenter: "middleearth-northwest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"middleearth-northwest"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -444,6 +474,21 @@ var expandedTokenTestCases = map[string]testCase{
|
||||||
Datacenter: "middleearth-northwest",
|
Datacenter: "middleearth-northwest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyServiceName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "web",
|
||||||
|
},
|
||||||
|
Datacenters: []string{"middleearth-northwest"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
TemplateName: api.ACLTemplatedPolicyNodeName,
|
||||||
|
TemplateVariables: &api.ACLTemplatedPolicyVariables{
|
||||||
|
Name: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,6 +38,24 @@
|
||||||
"Datacenter": "middleearth-northwest"
|
"Datacenter": "middleearth-northwest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"TemplatedPolicies": [
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/service",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "web"
|
||||||
|
},
|
||||||
|
"Datacenters": [
|
||||||
|
"middleearth-northwest",
|
||||||
|
"somewhere-east"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/node",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"Local": false,
|
"Local": false,
|
||||||
"AuthMethod": "bar",
|
"AuthMethod": "bar",
|
||||||
"ExpirationTime": "2020-05-22T19:52:31Z",
|
"ExpirationTime": "2020-05-22T19:52:31Z",
|
||||||
|
|
|
@ -19,3 +19,10 @@ Service Identities:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: web
|
||||||
|
Datacenters: middleearth-northwest, somewhere-east
|
||||||
|
builtin/node
|
||||||
|
Name: api
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -16,3 +16,10 @@ Service Identities:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: web
|
||||||
|
Datacenters: middleearth-northwest, somewhere-east
|
||||||
|
builtin/node
|
||||||
|
Name: api
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -181,6 +181,23 @@
|
||||||
"Datacenter": "middleearth-northwest"
|
"Datacenter": "middleearth-northwest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"TemplatedPolicies": [
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/service",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "web"
|
||||||
|
},
|
||||||
|
"Datacenters": [
|
||||||
|
"middleearth-northwest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/node",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"Local": false,
|
"Local": false,
|
||||||
"AuthMethod": "bar",
|
"AuthMethod": "bar",
|
||||||
"ExpirationTime": "2020-05-22T19:52:31Z",
|
"ExpirationTime": "2020-05-22T19:52:31Z",
|
||||||
|
|
|
@ -52,6 +52,37 @@ Node Identities:
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: web
|
||||||
|
Datacenters: middleearth-northwest
|
||||||
|
Description: synthetic policy generated from templated policy: builtin/service
|
||||||
|
Rules:
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
|
||||||
|
builtin/node
|
||||||
|
Name: api
|
||||||
|
Datacenters: all
|
||||||
|
Description: synthetic policy generated from templated policy: builtin/node
|
||||||
|
Rules:
|
||||||
|
node "api" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
|
||||||
Roles:
|
Roles:
|
||||||
Role Name: shire
|
Role Name: shire
|
||||||
ID: 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366
|
ID: 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366
|
||||||
|
|
|
@ -49,6 +49,37 @@ Node Identities:
|
||||||
policy = "read"
|
policy = "read"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: web
|
||||||
|
Datacenters: middleearth-northwest
|
||||||
|
Description: synthetic policy generated from templated policy: builtin/service
|
||||||
|
Rules:
|
||||||
|
service "web" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service "web-sidecar-proxy" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
|
||||||
|
builtin/node
|
||||||
|
Name: api
|
||||||
|
Datacenters: all
|
||||||
|
Description: synthetic policy generated from templated policy: builtin/node
|
||||||
|
Rules:
|
||||||
|
node "api" {
|
||||||
|
policy = "write"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
|
||||||
Roles:
|
Roles:
|
||||||
Role Name: shire
|
Role Name: shire
|
||||||
ID: 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366
|
ID: 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366
|
||||||
|
|
|
@ -39,6 +39,23 @@
|
||||||
"Datacenter": "middleearth-northwest"
|
"Datacenter": "middleearth-northwest"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"TemplatedPolicies": [
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/service",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "web"
|
||||||
|
},
|
||||||
|
"Datacenters": [
|
||||||
|
"middleearth-northwest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TemplateName": "builtin/node",
|
||||||
|
"TemplateVariables": {
|
||||||
|
"Name": "api"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"Local": false,
|
"Local": false,
|
||||||
"AuthMethod": "bar",
|
"AuthMethod": "bar",
|
||||||
"ExpirationTime": "2020-05-22T19:52:31Z",
|
"ExpirationTime": "2020-05-22T19:52:31Z",
|
||||||
|
|
|
@ -19,3 +19,10 @@ Service Identities:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: web
|
||||||
|
Datacenters: middleearth-northwest
|
||||||
|
builtin/node
|
||||||
|
Name: api
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -16,3 +16,10 @@ Service Identities:
|
||||||
gardener (Datacenters: middleearth-northwest)
|
gardener (Datacenters: middleearth-northwest)
|
||||||
Node Identities:
|
Node Identities:
|
||||||
bagend (Datacenter: middleearth-northwest)
|
bagend (Datacenter: middleearth-northwest)
|
||||||
|
Templated Policies:
|
||||||
|
builtin/service
|
||||||
|
Name: web
|
||||||
|
Datacenters: middleearth-northwest
|
||||||
|
builtin/node
|
||||||
|
Name: api
|
||||||
|
Datacenters: all
|
||||||
|
|
|
@ -28,22 +28,27 @@ type cmd struct {
|
||||||
http *flags.HTTPFlags
|
http *flags.HTTPFlags
|
||||||
help string
|
help string
|
||||||
|
|
||||||
tokenAccessorID string
|
tokenAccessorID string
|
||||||
policyIDs []string
|
policyIDs []string
|
||||||
appendPolicyIDs []string
|
appendPolicyIDs []string
|
||||||
policyNames []string
|
policyNames []string
|
||||||
appendPolicyNames []string
|
appendPolicyNames []string
|
||||||
roleIDs []string
|
roleIDs []string
|
||||||
appendRoleIDs []string
|
appendRoleIDs []string
|
||||||
roleNames []string
|
roleNames []string
|
||||||
appendRoleNames []string
|
appendRoleNames []string
|
||||||
serviceIdents []string
|
serviceIdents []string
|
||||||
nodeIdents []string
|
nodeIdents []string
|
||||||
appendNodeIdents []string
|
appendNodeIdents []string
|
||||||
appendServiceIdents []string
|
appendServiceIdents []string
|
||||||
description string
|
appendTemplatedPolicy string
|
||||||
showMeta bool
|
replaceTemplatedPolicy string
|
||||||
format string
|
appendTemplatedPolicyFile string
|
||||||
|
replaceTemplatedPolicyFile string
|
||||||
|
templatedPolicyVariables []string
|
||||||
|
description string
|
||||||
|
showMeta bool
|
||||||
|
format string
|
||||||
|
|
||||||
// DEPRECATED
|
// DEPRECATED
|
||||||
mergeServiceIdents bool
|
mergeServiceIdents bool
|
||||||
|
@ -89,6 +94,19 @@ func (c *cmd) init() {
|
||||||
c.flags.Var((*flags.AppendSliceValue)(&c.appendNodeIdents), "append-node-identity", "Name of a "+
|
c.flags.Var((*flags.AppendSliceValue)(&c.appendNodeIdents), "append-node-identity", "Name of a "+
|
||||||
"node identity to use for this token. This token retains existing node identities. May be "+
|
"node identity to use for this token. This token retains existing node identities. May be "+
|
||||||
"specified multiple times. Format is NODENAME:DATACENTER")
|
"specified multiple times. Format is NODENAME:DATACENTER")
|
||||||
|
c.flags.Var((*flags.AppendSliceValue)(&c.templatedPolicyVariables), "var", "Templated policy variables."+
|
||||||
|
" Must be used in combination with -replace-templated-policy or -append-templated-policy flags to specify required variables."+
|
||||||
|
" May be specified multiple times with different variables."+
|
||||||
|
" Format is VariableName:Value")
|
||||||
|
c.flags.StringVar(&c.appendTemplatedPolicy, "append-templated-policy", "", "The templated policy name to attach to the token's existing templated policies."+
|
||||||
|
"Use -var flag to specify variables when required. Token retains existing templated policies.")
|
||||||
|
c.flags.StringVar(&c.replaceTemplatedPolicy, "replace-templated-policy", "", "The templated policy name to use replace token's existing templated policies."+
|
||||||
|
" Use -var flag to specify variables when required. Overwrites token's existing templated policies.")
|
||||||
|
c.flags.StringVar(&c.appendTemplatedPolicyFile, "append-templated-policy-file", "", "Path to a file containing templated policy names and variables."+
|
||||||
|
" Works similarly to `-append-templated-policy`. The token retains existing templated policies.")
|
||||||
|
c.flags.StringVar(&c.replaceTemplatedPolicyFile, "replace-templated-policy-file", "", "Path to a file containing templated policy names and variables."+
|
||||||
|
" Works similarly to `-replace-templated-policy`. Overwrites the token's existing templated policies.")
|
||||||
|
|
||||||
c.flags.StringVar(
|
c.flags.StringVar(
|
||||||
&c.format,
|
&c.format,
|
||||||
"format",
|
"format",
|
||||||
|
@ -195,6 +213,24 @@ func (c *cmd) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasAppendTemplatedPolicies := len(c.appendTemplatedPolicy) > 0 || len(c.appendTemplatedPolicyFile) > 0
|
||||||
|
hasReplaceTemplatedPolicies := len(c.replaceTemplatedPolicy) > 0 || len(c.replaceTemplatedPolicyFile) > 0
|
||||||
|
|
||||||
|
if hasReplaceTemplatedPolicies && hasAppendTemplatedPolicies {
|
||||||
|
c.UI.Error("Cannot combine the use of -append-templated-policy flags with replace-templated-policy. " +
|
||||||
|
"To set or overwrite existing templated policies, use -replace-templated-policy or -replace-templated-policy-file. " +
|
||||||
|
"To append to existing templated policies, use -append-templated-policy or -append-templated-policy-file.")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
parsedTemplatedPolicies, err := acl.ExtractTemplatedPolicies(c.replaceTemplatedPolicy, c.replaceTemplatedPolicyFile, c.templatedPolicyVariables)
|
||||||
|
if hasAppendTemplatedPolicies {
|
||||||
|
parsedTemplatedPolicies, err = acl.ExtractTemplatedPolicies(c.appendTemplatedPolicy, c.appendTemplatedPolicyFile, c.templatedPolicyVariables)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if c.mergePolicies {
|
if c.mergePolicies {
|
||||||
c.UI.Warn("merge-policies is deprecated and will be removed in a future Consul version. " +
|
c.UI.Warn("merge-policies is deprecated and will be removed in a future Consul version. " +
|
||||||
"Use `append-policy-name` or `append-policy-id` instead.")
|
"Use `append-policy-name` or `append-policy-id` instead.")
|
||||||
|
@ -384,6 +420,12 @@ func (c *cmd) Run(args []string) int {
|
||||||
t.NodeIdentities = parsedNodeIdents
|
t.NodeIdentities = parsedNodeIdents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasReplaceTemplatedPolicies {
|
||||||
|
t.TemplatedPolicies = parsedTemplatedPolicies
|
||||||
|
} else {
|
||||||
|
t.TemplatedPolicies = append(t.TemplatedPolicies, parsedTemplatedPolicies...)
|
||||||
|
}
|
||||||
|
|
||||||
t, _, err = client.ACL().TokenUpdate(t, nil)
|
t, _, err = client.ACL().TokenUpdate(t, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.UI.Error(fmt.Sprintf("Failed to update token %s: %v", tok, err))
|
c.UI.Error(fmt.Sprintf("Failed to update token %s: %v", tok, err))
|
||||||
|
@ -432,6 +474,8 @@ Usage: consul acl token update [options]
|
||||||
$ consul acl token update -accessor-id abcd \
|
$ consul acl token update -accessor-id abcd \
|
||||||
-description "replication" \
|
-description "replication" \
|
||||||
-policy-name "token-replication" \
|
-policy-name "token-replication" \
|
||||||
-role-name "db-updater"
|
-role-name "db-updater" \
|
||||||
|
-templated-policy "builtin/service" \
|
||||||
|
-var "name:web"
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|
|
@ -120,6 +120,51 @@ func TestTokenUpdateCommand(t *testing.T) {
|
||||||
require.ElementsMatch(t, expected, responseToken.NodeIdentities)
|
require.ElementsMatch(t, expected, responseToken.NodeIdentities)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("replace-templated-policy", func(t *testing.T) {
|
||||||
|
token := create_token(
|
||||||
|
t,
|
||||||
|
client, &api.ACLToken{Description: "test", TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "api"}},
|
||||||
|
}},
|
||||||
|
&api.WriteOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
|
||||||
|
responseToken := run(t, []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-accessor-id=" + token.AccessorID,
|
||||||
|
"-token=root",
|
||||||
|
"-replace-templated-policy=builtin/node",
|
||||||
|
"-description=test token",
|
||||||
|
"-var=name:web",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Len(t, responseToken.TemplatedPolicies, 1)
|
||||||
|
require.Equal(t, api.ACLTemplatedPolicyNodeName, responseToken.TemplatedPolicies[0].TemplateName)
|
||||||
|
require.Equal(t, "web", responseToken.TemplatedPolicies[0].TemplateVariables.Name)
|
||||||
|
})
|
||||||
|
t.Run("append-templated-policy", func(t *testing.T) {
|
||||||
|
templatedPolicy := &api.ACLTemplatedPolicy{TemplateName: api.ACLTemplatedPolicyServiceName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "api"}}
|
||||||
|
token := create_token(
|
||||||
|
t,
|
||||||
|
client, &api.ACLToken{Description: "test", TemplatedPolicies: []*api.ACLTemplatedPolicy{
|
||||||
|
templatedPolicy,
|
||||||
|
}},
|
||||||
|
&api.WriteOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
|
||||||
|
responseToken := run(t, []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-accessor-id=" + token.AccessorID,
|
||||||
|
"-token=root",
|
||||||
|
"-append-templated-policy=builtin/node",
|
||||||
|
"-description=test token",
|
||||||
|
"-var=name:web",
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Len(t, responseToken.TemplatedPolicies, 2)
|
||||||
|
require.ElementsMatch(t, responseToken.TemplatedPolicies,
|
||||||
|
[]*api.ACLTemplatedPolicy{templatedPolicy, {TemplateName: api.ACLTemplatedPolicyNodeName, TemplateVariables: &api.ACLTemplatedPolicyVariables{Name: "web"}}})
|
||||||
|
})
|
||||||
// update with policy by name
|
// update with policy by name
|
||||||
t.Run("policy-name", func(t *testing.T) {
|
t.Run("policy-name", func(t *testing.T) {
|
||||||
token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"})
|
token := create_token(t, client, &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"})
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadFromFile(path string) (string, error) {
|
func LoadFromFile(path string) (string, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Failed to read file: %v", err)
|
return "", fmt.Errorf("Failed to read file: %v", err)
|
||||||
|
@ -47,7 +47,7 @@ func LoadDataSource(data string, testStdin io.Reader) (string, error) {
|
||||||
|
|
||||||
switch data[0] {
|
switch data[0] {
|
||||||
case '@':
|
case '@':
|
||||||
return loadFromFile(data[1:])
|
return LoadFromFile(data[1:])
|
||||||
case '-':
|
case '-':
|
||||||
if len(data) > 1 {
|
if len(data) > 1 {
|
||||||
return data, nil
|
return data, nil
|
||||||
|
@ -67,7 +67,7 @@ func LoadDataSourceNoRaw(data string, testStdin io.Reader) (string, error) {
|
||||||
return loadFromStdin(testStdin)
|
return loadFromStdin(testStdin)
|
||||||
}
|
}
|
||||||
|
|
||||||
return loadFromFile(data)
|
return LoadFromFile(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseConfigEntry(data string) (api.ConfigEntry, error) {
|
func ParseConfigEntry(data string) (api.ConfigEntry, error) {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -97,6 +97,7 @@ require (
|
||||||
github.com/ryanuber/columnize v2.1.2+incompatible
|
github.com/ryanuber/columnize v2.1.2+incompatible
|
||||||
github.com/shirou/gopsutil/v3 v3.22.8
|
github.com/shirou/gopsutil/v3 v3.22.8
|
||||||
github.com/stretchr/testify v1.8.3
|
github.com/stretchr/testify v1.8.3
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
github.com/zclconf/go-cty v1.2.0
|
github.com/zclconf/go-cty v1.2.0
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
go.opentelemetry.io/otel v1.16.0
|
go.opentelemetry.io/otel v1.16.0
|
||||||
|
@ -245,6 +246,7 @@ require (
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||||
github.com/vmware/govmomi v0.18.0 // indirect
|
github.com/vmware/govmomi v0.18.0 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.11.0 // indirect
|
go.mongodb.org/mongo-driver v1.11.0 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -937,8 +937,13 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+
|
||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
|
|
|
@ -190,6 +190,8 @@ require (
|
||||||
github.com/testcontainers/testcontainers-go v0.22.0 // indirect
|
github.com/testcontainers/testcontainers-go v0.22.0 // indirect
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.12.1 // indirect
|
github.com/zclconf/go-cty v1.12.1 // indirect
|
||||||
go.etcd.io/bbolt v1.3.7 // indirect
|
go.etcd.io/bbolt v1.3.7 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.11.0 // indirect
|
go.mongodb.org/mongo-driver v1.11.0 // indirect
|
||||||
|
|
|
@ -774,8 +774,13 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+
|
||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
|
@ -186,6 +186,8 @@ require (
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||||
go.etcd.io/bbolt v1.3.7 // indirect
|
go.etcd.io/bbolt v1.3.7 // indirect
|
||||||
go.mongodb.org/mongo-driver v1.11.0 // indirect
|
go.mongodb.org/mongo-driver v1.11.0 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
|
|
@ -763,8 +763,13 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+
|
||||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|
Loading…
Reference in New Issue