secrets/consul: Add support for generating tokens with service and node identities (#15295)
Co-authored-by: Thomas L. Kula <kula@tproa.net>
This commit is contained in:
parent
0dc6728228
commit
738753b187
|
@ -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"
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/consul: Add support for Consul node-identities and service-identities
|
||||
```
|
|
@ -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",
|
||||
|
|
|
@ -74,13 +74,6 @@ updated attributes.
|
|||
| :----- | :-------------------- |
|
||||
| `POST` | `/consul/roles/:name` |
|
||||
|
||||
### Parameters for Consul versions 1.7 and above
|
||||
|
||||
- `consul_namespace` `(string: "")` <EnterpriseAlert inline /> - 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: "")` <EnterpriseAlert inline /> - 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: "")` <EnterpriseAlert inline /> - 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: <node identity or identities>)` - 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: <required>)` – 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: <role or roles>)` – 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: <service identity or identities>)` - 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
|
||||
|
|
|
@ -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="<management-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="<management-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=<duration>` to the command above with "duration" being a string with a time
|
||||
|
|
Loading…
Reference in New Issue