diff --git a/builtin/logical/consul/backend_test.go b/builtin/logical/consul/backend_test.go
index 5474f7cb5..8e72defe4 100644
--- a/builtin/logical/consul/backend_test.go
+++ b/builtin/logical/consul/backend_test.go
@@ -564,8 +564,7 @@ func TestBackend_role_lease(t *testing.T) {
})
}
-func testAccStepConfig(
- t *testing.T, config map[string]interface{}) logicaltest.TestStep {
+func testAccStepConfig(t *testing.T, config map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "config/access",
@@ -573,8 +572,7 @@ func testAccStepConfig(
}
}
-func testAccStepReadToken(
- t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
+func testAccStepReadToken(t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
@@ -610,8 +608,7 @@ func testAccStepReadToken(
}
}
-func testAccStepReadManagementToken(
- t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
+func testAccStepReadManagementToken(t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
@@ -1082,6 +1079,239 @@ func testBackendEntPartition(t *testing.T) {
}
}
+func TestBackendRenewRevokeRolesAndIdentities(t *testing.T) {
+ config := logical.TestBackendConfig()
+ config.StorageView = &logical.InmemStorage{}
+ b, err := Factory(context.Background(), config)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
+ defer cleanup()
+
+ connData := map[string]interface{}{
+ "address": consulConfig.Address(),
+ "token": consulConfig.Token,
+ }
+
+ req := &logical.Request{
+ Storage: config.StorageView,
+ Operation: logical.UpdateOperation,
+ Path: "config/access",
+ Data: connData,
+ }
+ resp, err := b.HandleRequest(context.Background(), req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := map[string]struct {
+ RoleName string
+ RoleData map[string]interface{}
+ }{
+ "just role": {
+ "r",
+ map[string]interface{}{
+ "consul_roles": []string{"role-test"},
+ "lease": "6h",
+ },
+ },
+ "role and policies": {
+ "rp",
+ map[string]interface{}{
+ "policies": []string{"test"},
+ "consul_roles": []string{"role-test"},
+ "lease": "6h",
+ },
+ },
+ "service identity": {
+ "si",
+ map[string]interface{}{
+ "service_identities": "service1",
+ "lease": "6h",
+ },
+ },
+ "service identity and policies": {
+ "sip",
+ map[string]interface{}{
+ "policies": []string{"test"},
+ "service_identities": "service1",
+ "lease": "6h",
+ },
+ },
+ "service identity and role": {
+ "sir",
+ map[string]interface{}{
+ "consul_roles": []string{"role-test"},
+ "service_identities": "service1",
+ "lease": "6h",
+ },
+ },
+ "service identity and role and policies": {
+ "sirp",
+ map[string]interface{}{
+ "policies": []string{"test"},
+ "consul_roles": []string{"role-test"},
+ "service_identities": "service1",
+ "lease": "6h",
+ },
+ },
+ "node identity": {
+ "ni",
+ map[string]interface{}{
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and policies": {
+ "nip",
+ map[string]interface{}{
+ "policies": []string{"test"},
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and role": {
+ "nir",
+ map[string]interface{}{
+ "consul_roles": []string{"role-test"},
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and role and policies": {
+ "nirp",
+ map[string]interface{}{
+ "consul_roles": []string{"role-test"},
+ "service_identities": "service1",
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and service identity": {
+ "nisi",
+ map[string]interface{}{
+ "service_identities": "service1",
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and service identity and policies": {
+ "nisip",
+ map[string]interface{}{
+ "policies": []string{"test"},
+ "service_identities": "service1",
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and service identity and role": {
+ "nisir",
+ map[string]interface{}{
+ "consul_roles": []string{"role-test"},
+ "service_identities": "service1",
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ "node identity and service identity and role and policies": {
+ "nisirp",
+ map[string]interface{}{
+ "policies": []string{"test"},
+ "consul_roles": []string{"role-test"},
+ "service_identities": "service1",
+ "node_identities": []string{"node1:dc1"},
+ "lease": "6h",
+ },
+ },
+ }
+
+ for description, tc := range cases {
+ t.Logf("Testing: %s", description)
+
+ req.Operation = logical.UpdateOperation
+ req.Path = fmt.Sprintf("roles/%s", tc.RoleName)
+ req.Data = tc.RoleData
+ resp, err = b.HandleRequest(context.Background(), req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ req.Operation = logical.ReadOperation
+ req.Path = fmt.Sprintf("creds/%s", tc.RoleName)
+ resp, err = b.HandleRequest(context.Background(), req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resp == nil {
+ t.Fatal("resp nil")
+ }
+ if resp.IsError() {
+ t.Fatalf("resp is error: %v", resp.Error())
+ }
+
+ generatedSecret := resp.Secret
+ generatedSecret.TTL = 6 * time.Hour
+
+ var d struct {
+ Token string `mapstructure:"token"`
+ Accessor string `mapstructure:"accessor"`
+ }
+ if err := mapstructure.Decode(resp.Data, &d); err != nil {
+ t.Fatal(err)
+ }
+ t.Logf("Generated token: %s with accessor %s", d.Token, d.Accessor)
+
+ // Build a client and verify that the credentials work
+ consulapiConfig := consulapi.DefaultNonPooledConfig()
+ consulapiConfig.Address = connData["address"].(string)
+ consulapiConfig.Token = d.Token
+ client, err := consulapi.NewClient(consulapiConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Log("Verifying that the generated token works...")
+ _, err = client.Catalog(), nil
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ req.Operation = logical.RenewOperation
+ req.Secret = generatedSecret
+ resp, err = b.HandleRequest(context.Background(), req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if resp == nil {
+ t.Fatal("got nil response from renew")
+ }
+
+ req.Operation = logical.RevokeOperation
+ resp, err = b.HandleRequest(context.Background(), req)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Build a management client and verify that the token does not exist anymore
+ consulmgmtConfig := consulapi.DefaultNonPooledConfig()
+ consulmgmtConfig.Address = connData["address"].(string)
+ consulmgmtConfig.Token = connData["token"].(string)
+ mgmtclient, err := consulapi.NewClient(consulmgmtConfig)
+
+ q := &consulapi.QueryOptions{
+ Datacenter: "DC1",
+ }
+
+ t.Log("Verifying that the generated token does not exist...")
+ _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q)
+ if err == nil {
+ t.Fatal("err: expected error")
+ }
+ }
+}
+
const testPolicy = `
key "" {
policy = "write"
diff --git a/builtin/logical/consul/path_roles.go b/builtin/logical/consul/path_roles.go
index a73f674e6..967dd646c 100644
--- a/builtin/logical/consul/path_roles.go
+++ b/builtin/logical/consul/path_roles.go
@@ -89,6 +89,18 @@ created within. Defaults to 'default'. Available in Consul 1.7 and above.`,
Description: `Indicates which admin partition that the token
will be created within. Defaults to 'default'. Available in Consul 1.11 and above.`,
},
+
+ "service_identities": {
+ Type: framework.TypeStringSlice,
+ Description: `List of Service Identities to attach to the
+token, separated by semicolons. Available in Consul 1.5 or above.`,
+ },
+
+ "node_identities": {
+ Type: framework.TypeStringSlice,
+ Description: `List of Node Identities to attach to the
+token. Available in Consul 1.8.1 or above.`,
+ },
},
Callbacks: map[logical.Operation]framework.OperationFunc{
@@ -149,6 +161,13 @@ func (b *backend) pathRolesRead(ctx context.Context, req *logical.Request, d *fr
if len(roleConfigData.ConsulRoles) > 0 {
resp.Data["consul_roles"] = roleConfigData.ConsulRoles
}
+ if len(roleConfigData.ServiceIdentities) > 0 {
+ resp.Data["service_identities"] = roleConfigData.ServiceIdentities
+ }
+ if len(roleConfigData.NodeIdentities) > 0 {
+ resp.Data["node_identities"] = roleConfigData.NodeIdentities
+ }
+
return resp, nil
}
@@ -157,17 +176,19 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
policy := d.Get("policy").(string)
policies := d.Get("policies").([]string)
roles := d.Get("consul_roles").([]string)
+ serviceIdentities := d.Get("service_identities").([]string)
+ nodeIdentities := d.Get("node_identities").([]string)
switch tokenType {
case "client":
- if policy == "" && len(policies) == 0 && len(roles) == 0 {
+ if policy == "" && len(policies) == 0 && len(roles) == 0 &&
+ len(serviceIdentities) == 0 && len(nodeIdentities) == 0 {
return logical.ErrorResponse(
- "Use either a policy document, a list of policies, or a list of roles, depending on your Consul version"), nil
+ "Use either a policy document, a list of policies or roles, or a set of service or node identities, depending on your Consul version"), nil
}
case "management":
default:
- return logical.ErrorResponse(
- "token_type must be \"client\" or \"management\""), nil
+ return logical.ErrorResponse("token_type must be \"client\" or \"management\""), nil
}
policyRaw, err := base64.StdEncoding.DecodeString(policy)
@@ -198,15 +219,17 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
namespace := d.Get("consul_namespace").(string)
partition := d.Get("partition").(string)
entry, err := logical.StorageEntryJSON("policy/"+name, roleConfig{
- Policy: string(policyRaw),
- Policies: policies,
- ConsulRoles: roles,
- TokenType: tokenType,
- TTL: ttl,
- MaxTTL: maxTTL,
- Local: local,
- ConsulNamespace: namespace,
- Partition: partition,
+ Policy: string(policyRaw),
+ Policies: policies,
+ ConsulRoles: roles,
+ ServiceIdentities: serviceIdentities,
+ NodeIdentities: nodeIdentities,
+ TokenType: tokenType,
+ TTL: ttl,
+ MaxTTL: maxTTL,
+ Local: local,
+ ConsulNamespace: namespace,
+ Partition: partition,
})
if err != nil {
return nil, err
@@ -228,13 +251,15 @@ func (b *backend) pathRolesDelete(ctx context.Context, req *logical.Request, d *
}
type roleConfig struct {
- Policy string `json:"policy"`
- Policies []string `json:"policies"`
- ConsulRoles []string `json:"consul_roles"`
- TTL time.Duration `json:"lease"`
- MaxTTL time.Duration `json:"max_ttl"`
- TokenType string `json:"token_type"`
- Local bool `json:"local"`
- ConsulNamespace string `json:"consul_namespace"`
- Partition string `json:"partition"`
+ Policy string `json:"policy"`
+ Policies []string `json:"policies"`
+ ConsulRoles []string `json:"consul_roles"`
+ ServiceIdentities []string `json:"service_identities"`
+ NodeIdentities []string `json:"node_identities"`
+ TTL time.Duration `json:"lease"`
+ MaxTTL time.Duration `json:"max_ttl"`
+ TokenType string `json:"token_type"`
+ Local bool `json:"local"`
+ ConsulNamespace string `json:"consul_namespace"`
+ Partition string `json:"partition"`
}
diff --git a/builtin/logical/consul/path_token.go b/builtin/logical/consul/path_token.go
index f309fa35f..7568774f3 100644
--- a/builtin/logical/consul/path_token.go
+++ b/builtin/logical/consul/path_token.go
@@ -3,6 +3,7 @@ package consul
import (
"context"
"fmt"
+ "strings"
"time"
"github.com/hashicorp/consul/api"
@@ -103,13 +104,18 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr
})
}
+ aclServiceIdentities := parseServiceIdentities(roleConfigData.ServiceIdentities)
+ aclNodeIdentities := parseNodeIdentities(roleConfigData.NodeIdentities)
+
token, _, err := c.ACL().TokenCreate(&api.ACLToken{
- Description: tokenName,
- Policies: policyLinks,
- Roles: roleLinks,
- Local: roleConfigData.Local,
- Namespace: roleConfigData.ConsulNamespace,
- Partition: roleConfigData.Partition,
+ Description: tokenName,
+ Policies: policyLinks,
+ Roles: roleLinks,
+ ServiceIdentities: aclServiceIdentities,
+ NodeIdentities: aclNodeIdentities,
+ Local: roleConfigData.Local,
+ Namespace: roleConfigData.ConsulNamespace,
+ Partition: roleConfigData.Partition,
}, writeOpts)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
@@ -132,3 +138,35 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr
return s, nil
}
+
+func parseServiceIdentities(data []string) []*api.ACLServiceIdentity {
+ aclServiceIdentities := []*api.ACLServiceIdentity{}
+
+ for _, serviceIdentity := range data {
+ entry := &api.ACLServiceIdentity{}
+ components := strings.Split(serviceIdentity, ":")
+ entry.ServiceName = components[0]
+ if len(components) == 2 {
+ entry.Datacenters = strings.Split(components[1], ",")
+ }
+ aclServiceIdentities = append(aclServiceIdentities, entry)
+ }
+
+ return aclServiceIdentities
+}
+
+func parseNodeIdentities(data []string) []*api.ACLNodeIdentity {
+ aclNodeIdentities := []*api.ACLNodeIdentity{}
+
+ for _, nodeIdentity := range data {
+ entry := &api.ACLNodeIdentity{}
+ components := strings.Split(nodeIdentity, ":")
+ entry.NodeName = components[0]
+ if len(components) > 1 {
+ entry.Datacenter = components[1]
+ }
+ aclNodeIdentities = append(aclNodeIdentities, entry)
+ }
+
+ return aclNodeIdentities
+}
diff --git a/builtin/logical/consul/path_token_test.go b/builtin/logical/consul/path_token_test.go
new file mode 100644
index 000000000..e83ceee2d
--- /dev/null
+++ b/builtin/logical/consul/path_token_test.go
@@ -0,0 +1,104 @@
+package consul
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/hashicorp/consul/api"
+)
+
+func Test_parseServiceIdentities(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ want []*api.ACLServiceIdentity
+ }{
+ {
+ name: "No datacenters",
+ args: []string{"myservice-1"},
+ want: []*api.ACLServiceIdentity{{ServiceName: "myservice-1", Datacenters: nil}},
+ },
+ {
+ name: "One datacenter",
+ args: []string{"myservice-1:dc1"},
+ want: []*api.ACLServiceIdentity{{ServiceName: "myservice-1", Datacenters: []string{"dc1"}}},
+ },
+ {
+ name: "Multiple datacenters",
+ args: []string{"myservice-1:dc1,dc2,dc3"},
+ want: []*api.ACLServiceIdentity{{ServiceName: "myservice-1", Datacenters: []string{"dc1", "dc2", "dc3"}}},
+ },
+ {
+ name: "Missing service name with datacenter",
+ args: []string{":dc1"},
+ want: []*api.ACLServiceIdentity{{ServiceName: "", Datacenters: []string{"dc1"}}},
+ },
+ {
+ name: "Missing service name and missing datacenter",
+ args: []string{""},
+ want: []*api.ACLServiceIdentity{{ServiceName: "", Datacenters: nil}},
+ },
+ {
+ name: "Multiple service identities",
+ args: []string{"myservice-1:dc1", "myservice-2:dc1", "myservice-3:dc1,dc2"},
+ want: []*api.ACLServiceIdentity{
+ {ServiceName: "myservice-1", Datacenters: []string{"dc1"}},
+ {ServiceName: "myservice-2", Datacenters: []string{"dc1"}},
+ {ServiceName: "myservice-3", Datacenters: []string{"dc1", "dc2"}},
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := parseServiceIdentities(tt.args); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("parseServiceIdentities() = {%s:%v}, want {%s:%v}", got[0].ServiceName, got[0].Datacenters, tt.want[0].ServiceName, tt.want[0].Datacenters)
+ }
+ })
+ }
+}
+
+func Test_parseNodeIdentities(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ want []*api.ACLNodeIdentity
+ }{
+ {
+ name: "No datacenter",
+ args: []string{"server-1"},
+ want: []*api.ACLNodeIdentity{{NodeName: "server-1", Datacenter: ""}},
+ },
+ {
+ name: "One datacenter",
+ args: []string{"server-1:dc1"},
+ want: []*api.ACLNodeIdentity{{NodeName: "server-1", Datacenter: "dc1"}},
+ },
+ {
+ name: "Missing node name with datacenter",
+ args: []string{":dc1"},
+ want: []*api.ACLNodeIdentity{{NodeName: "", Datacenter: "dc1"}},
+ },
+ {
+ name: "Missing node name and missing datacenter",
+ args: []string{""},
+ want: []*api.ACLNodeIdentity{{NodeName: "", Datacenter: ""}},
+ },
+ {
+ name: "Multiple node identities",
+ args: []string{"server-1:dc1", "server-2:dc1", "server-3:dc1"},
+ want: []*api.ACLNodeIdentity{
+ {NodeName: "server-1", Datacenter: "dc1"},
+ {NodeName: "server-2", Datacenter: "dc1"},
+ {NodeName: "server-3", Datacenter: "dc1"},
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := parseNodeIdentities(tt.args); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("parseNodeIdentities() = {%s:%s}, want {%s:%s}", got[0].NodeName, got[0].Datacenter, tt.want[0].NodeName, tt.want[0].Datacenter)
+ }
+ })
+ }
+}
diff --git a/changelog/15295.txt b/changelog/15295.txt
new file mode 100644
index 000000000..0fbf4b5ed
--- /dev/null
+++ b/changelog/15295.txt
@@ -0,0 +1,3 @@
+```release-note:improvement
+secrets/consul: Add support for Consul node-identities and service-identities
+```
\ No newline at end of file
diff --git a/helper/testhelpers/consul/consulhelper.go b/helper/testhelpers/consul/consulhelper.go
index e88149079..3facba58f 100644
--- a/helper/testhelpers/consul/consulhelper.go
+++ b/helper/testhelpers/consul/consulhelper.go
@@ -148,7 +148,7 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool, boots
currVersion, _ := goversion.NewVersion(version)
roleVersion, _ := goversion.NewVersion("1.5")
if currVersion.GreaterThanOrEqual(roleVersion) {
- ACLList := []*consulapi.ACLLink{{Name: "test"}}
+ ACLList := []*consulapi.ACLTokenRoleLink{{Name: "test"}}
role := &consulapi.ACLRole{
Name: "role-test",
diff --git a/website/content/api-docs/secret/consul.mdx b/website/content/api-docs/secret/consul.mdx
index eacc7e3f7..56a41926a 100644
--- a/website/content/api-docs/secret/consul.mdx
+++ b/website/content/api-docs/secret/consul.mdx
@@ -74,13 +74,6 @@ updated attributes.
| :----- | :-------------------- |
| `POST` | `/consul/roles/:name` |
-### Parameters for Consul versions 1.7 and above
-
-- `consul_namespace` `(string: "")` - Specifies the Consul namespace the token
- will be generated within. The namespace must exist, and the policies or roles assigned to the
- Vault role must also exist inside the given Consul namespace. If not provided, the "default"
- namespace is used.
-
### Parameters for Consul versions 1.11 and above
- `partition` `(string: "")` - Specifies the Consul admin partition the token
@@ -88,14 +81,6 @@ updated attributes.
Vault role must also exist inside the given partition. If not provided, the "default"
partition is used.
-To create a client token within a particular Consul namespace:
-
-```json
-{
- "consul_namespace": "ns1"
-}
-```
-
To create a client token within a particular Consul admin partition:
```json
@@ -104,7 +89,34 @@ To create a client token within a particular Consul admin partition:
}
```
-### Parameters for Consul versions 1.4 and above
+### Parameters for Consul versions 1.8 and above
+
+- `consul_namespace` `(string: "")` - Specifies the Consul namespace the token
+ will be generated within. The namespace must exist, and the policies or roles assigned to the
+ Vault role must also exist inside the given Consul namespace. If not provided, the "default"
+ namespace is used.
+
+- `node_identities` `(list: )` - The list of node identities to
+assign to the generated token. This may be a comma-separated list to attach multiple node identities
+to a token.
+
+To create a client token within a particular Consul namespace:
+
+```json
+{
+ "consul_namespace": "ns1"
+}
+```
+
+To create a client token with node identities attached:
+
+```json
+{
+ "node_identities": "client-1:dc1,client-2:dc1"
+}
+```
+
+### Parameters for Consul versions 1.5 and above
- `name` `(string: )` – Specifies the name of an existing role against
which to create this Consul credential. This is part of the request URL.
@@ -125,6 +137,10 @@ To create a client token within a particular Consul admin partition:
- `consul_roles` `(list: )` – The list of Consul roles to assign to the
generated token. Either `policies` or `consul_roles` are required for Consul 1.5 and above.
+- `service_identities` `(list: )` - The list of
+ service identities to assign to the generated token. This may be a semicolon-separated list to
+ attach multiple service identities to a token.
+
- `local` `(bool: false)` - Indicates that the token should not be replicated
globally and instead be local to the current datacenter. Only available in Consul
1.4 and greater.
@@ -163,6 +179,14 @@ To create a client token with defined roles:
}
```
+To create a client token with service identities attached:
+
+```json
+{
+ "service_identities": "myservice-1:dc1,dc2;myservice-2:dc1"
+}
+```
+
### Sample Request
```shell-session
diff --git a/website/content/docs/secrets/consul.mdx b/website/content/docs/secrets/consul.mdx
index 738b7ea27..f7e41cca1 100644
--- a/website/content/docs/secrets/consul.mdx
+++ b/website/content/docs/secrets/consul.mdx
@@ -38,39 +38,39 @@ management tool.
If you have already bootstrapped the ACL system of your Consul cluster, you
will need to give Vault a management token:
- - In Consul versions below 1.4, acquire a [management token][consul-mgmt-token] from Consul, using the
- `acl_master_token` from your Consul configuration file or another management
- token:
+ In Consul versions below 1.4, acquire a [management token][consul-mgmt-token] from Consul, using the
+ `acl_master_token` from your Consul configuration file or another management
+ token:
- ```sh
- $ curl \
- --header "X-Consul-Token: my-management-token" \
- --request PUT \
- --data '{"Name": "sample", "Type": "management"}' \
- https://consul.rocks/v1/acl/create
- ```
+ ```sh
+ $ curl \
+ --header "X-Consul-Token: my-management-token" \
+ --request PUT \
+ --data '{"Name": "sample", "Type": "management"}' \
+ https://consul.rocks/v1/acl/create
+ ```
- Vault must have a management type token so that it can create and revoke ACL
- tokens. The response will return a new token:
+ Vault must have a management type token so that it can create and revoke ACL
+ tokens. The response will return a new token:
- ```json
- {
- "ID": "7652ba4c-0f6e-8e75-5724-5e083d72cfe4"
- }
- ```
+ ```json
+ {
+ "ID": "7652ba4c-0f6e-8e75-5724-5e083d72cfe4"
+ }
+ ```
- - For Consul 1.4 and above, use the command line to generate a token with the appropriate policy:
+ For Consul 1.4 and above, use the command line to generate a token with the appropriate policy:
- ```shell-session
- $ CONSUL_HTTP_TOKEN="" consul acl token create -policy-name="global-management"
- AccessorID: 865dc5e9-e585-3180-7b49-4ddc0fc45135
- SecretID: ef35f0f1-885b-0cab-573c-7c91b65a7a7e
- Description:
- Local: false
- Create Time: 2018-10-22 17:40:24.128188 -0700 PDT
- Policies:
- 00000000-0000-0000-0000-000000000001 - global-management
- ```
+ ```shell-session
+ $ CONSUL_HTTP_TOKEN="" consul acl token create -policy-name="global-management"
+ AccessorID: 865dc5e9-e585-3180-7b49-4ddc0fc45135
+ SecretID: ef35f0f1-885b-0cab-573c-7c91b65a7a7e
+ Description:
+ Local: false
+ Create Time: 2018-10-22 17:40:24.128188 -0700 PDT
+ Policies:
+ 00000000-0000-0000-0000-000000000001 - global-management
+ ```
1. Configure Vault to connect and authenticate to Consul:
@@ -82,7 +82,8 @@ management tool.
```
1. Configure a role that maps a name in Vault to a Consul ACL policy. Depending on your Consul version,
- you will either provide a policy document and a token_type, or a set of policies.
+ you will either provide a policy document and a token_type, a list of policies or roles, or a set of
+ service or node identities.
When users generate credentials, they are generated against this role.
For Consul versions below 1.4, the policy must be base64-encoded. The policy language is
@@ -103,14 +104,26 @@ management tool.
Success! Data written to: consul/roles/my-role
```
- For Consul versions 1.5 and above, [generate a role in Consul](https://www.consul.io/api/acl/roles), and
- proceed to link it to the role:
+ For Consul versions 1.5 and above, [generate a role in Consul](https://www.consul.io/api/acl/roles) and
+ proceed to link it to the role, or [attach a Consul service identity](https://www.consul.io/commands/acl/token/create#service-identity) to the role:
```shell-session
$ vault write consul/roles/my-role consul_roles="api-server"
Success! Data written to: consul/roles/my-role
```
+ ```shell-session
+ $ vault write consul/roles/my-role service_identities="myservice:dc1,dc2"
+ Success! Data written to: consul/roles/my-role
+ ```
+
+ For Consul versions 1.8 and above, [attach a Consul node identity](https://www.consul.io/commands/acl/token/create#node-identity) to the role.
+
+ ```shell-session
+ $ vault write consul/roles/my-role node_identities="server-1:dc1"
+ Success! Data written to: consul/roles/my-role
+ ```
+
-> **Token lease duration:** If you do not specify a value for `ttl` (or `lease` for Consul versions below 1.4) the
tokens created using Vault's Consul secrets engine are created with a Time To Live (TTL) of 30 days. You can change
the lease duration by passing `-ttl=` to the command above with "duration" being a string with a time