From 91f5069c034157be716e24d8b46ee73bd5b81f52 Mon Sep 17 00:00:00 2001 From: Robert <17119716+robmonte@users.noreply.github.com> Date: Wed, 16 Feb 2022 19:31:08 -0600 Subject: [PATCH] secret/consul: Add Consul ACL roles support (#14014) Co-authored-by: Brandon Ingalls --- builtin/logical/consul/backend_test.go | 171 +++++++++++++++++---- builtin/logical/consul/path_roles.go | 78 ++++++---- builtin/logical/consul/path_token.go | 24 +-- changelog/14014.txt | 3 + go.mod | 2 +- go.sum | 2 + helper/testhelpers/consul/consulhelper.go | 96 +++++++----- website/content/api-docs/secret/consul.mdx | 53 ++++--- website/content/docs/secrets/consul.mdx | 18 ++- 9 files changed, 309 insertions(+), 138 deletions(-) create mode 100644 changelog/14014.txt diff --git a/builtin/logical/consul/backend_test.go b/builtin/logical/consul/backend_test.go index d2c7a481c..843338bf4 100644 --- a/builtin/logical/consul/backend_test.go +++ b/builtin/logical/consul/backend_test.go @@ -22,7 +22,7 @@ func TestBackend_Config_Access(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() - testBackendConfigAccess(t, "1.3.0") + testBackendConfigAccess(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() @@ -82,7 +82,7 @@ func TestBackend_Renew_Revoke(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() - testBackendRenewRevoke(t, "1.3.0") + testBackendRenewRevoke(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() @@ -118,7 +118,7 @@ func testBackendRenewRevoke(t *testing.T, version string) { Path: "config/access", Data: connData, } - resp, err := b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -128,14 +128,14 @@ func testBackendRenewRevoke(t *testing.T, version string) { "policy": base64.StdEncoding.EncodeToString([]byte(testPolicy)), "lease": "6h", } - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test" - resp, err = b.HandleRequest(context.Background(), req) + resp, err := b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -186,7 +186,7 @@ func testBackendRenewRevoke(t *testing.T, version string) { } req.Operation = logical.RevokeOperation - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -223,7 +223,7 @@ func testBackendRenewRevoke14(t *testing.T, version string) { Path: "config/access", Data: connData, } - resp, err := b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -233,14 +233,14 @@ func testBackendRenewRevoke14(t *testing.T, version string) { "policies": []string{"test"}, "lease": "6h", } - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test" - resp, err = b.HandleRequest(context.Background(), req) + resp, err := b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -289,7 +289,7 @@ func testBackendRenewRevoke14(t *testing.T, version string) { } req.Operation = logical.RevokeOperation - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -335,7 +335,7 @@ func TestBackend_LocalToken(t *testing.T) { Path: "config/access", Data: connData, } - resp, err := b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -346,7 +346,7 @@ func TestBackend_LocalToken(t *testing.T) { "ttl": "6h", "local": false, } - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -357,14 +357,14 @@ func TestBackend_LocalToken(t *testing.T) { "ttl": "6h", "local": true, } - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test" - resp, err = b.HandleRequest(context.Background(), req) + resp, err := b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -447,12 +447,14 @@ func TestBackend_Management(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() - testBackendManagement(t, "1.3.0") + testBackendManagement(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() testBackendManagement(t, "1.4.4") }) + + testBackendManagement(t, "1.10.8") }) } @@ -487,15 +489,16 @@ func TestBackend_Basic(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() - testBackendBasic(t, "1.3.0") + testBackendBasic(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() t.Run("legacy", func(t *testing.T) { t.Parallel() - testBackendRenewRevoke(t, "1.4.4") + testBackendBasic(t, "1.4.4") }) - testBackendBasic(t, "1.4.4") + + testBackendBasic(t, "1.10.8") }) }) } @@ -702,6 +705,119 @@ func testAccStepDeletePolicy(t *testing.T, name string) logicaltest.TestStep { } } +func TestBackend_Roles(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) + 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, + } + _, err = b.HandleRequest(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + // Create the consul_roles role + req.Path = "roles/test-consul-roles" + req.Data = map[string]interface{}{ + "consul_roles": []string{"role-test"}, + "lease": "6h", + } + _, err = b.HandleRequest(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + req.Operation = logical.ReadOperation + req.Path = "creds/test-consul-roles" + 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 consul_roles 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 + _, 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) + if err != nil { + t.Fatal(err) + } + 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") + } +} + func TestBackend_Enterprise_Namespace(t *testing.T) { if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { t.Skip("Skipping: No enterprise license found") @@ -740,7 +856,7 @@ func testBackendEntNamespace(t *testing.T) { Path: "config/access", Data: connData, } - resp, err := b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -752,14 +868,14 @@ func testBackendEntNamespace(t *testing.T) { "lease": "6h", "consul_namespace": "ns1", } - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test-ns" - resp, err = b.HandleRequest(context.Background(), req) + resp, err := b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -813,7 +929,7 @@ func testBackendEntNamespace(t *testing.T) { } req.Operation = logical.RevokeOperation - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -860,7 +976,7 @@ func testBackendEntPartition(t *testing.T) { Path: "config/access", Data: connData, } - resp, err := b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -872,14 +988,14 @@ func testBackendEntPartition(t *testing.T) { "lease": "6h", "partition": "part1", } - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test-part" - resp, err = b.HandleRequest(context.Background(), req) + resp, err := b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -933,7 +1049,7 @@ func testBackendEntPartition(t *testing.T) { } req.Operation = logical.RevokeOperation - resp, err = b.HandleRequest(context.Background(), req) + _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } @@ -961,5 +1077,4 @@ func testBackendEntPartition(t *testing.T) { const testPolicy = ` key "" { policy = "write" -} -` +}` diff --git a/builtin/logical/consul/path_roles.go b/builtin/logical/consul/path_roles.go index ee82dbc84..a73f674e6 100644 --- a/builtin/logical/consul/path_roles.go +++ b/builtin/logical/consul/path_roles.go @@ -37,23 +37,29 @@ for 'client' tokens. Required for Consul pre-1.4.`, "policies": { Type: framework.TypeCommaStringSlice, - Description: `List of policies to attach to the token. Required -for Consul 1.4 or above.`, + Description: `List of policies to attach to the token. Either "policies" +or "consul_roles" are required for Consul 1.5 and above, or just "policies" if +using Consul 1.4.`, + }, + + "consul_roles": { + Type: framework.TypeCommaStringSlice, + Description: `List of Consul roles to attach to the token. Either "policies" +or "consul_roles" are required for Consul 1.5 and above.`, }, "local": { Type: framework.TypeBool, Description: `Indicates that the token should not be replicated globally -and instead be local to the current datacenter. Available in Consul 1.4 and above.`, +and instead be local to the current datacenter. Available in Consul 1.4 and above.`, }, "token_type": { Type: framework.TypeString, Default: "client", - Description: `Which type of token to create: 'client' -or 'management'. If a 'management' token, -the "policy" parameter is not required. -Defaults to 'client'.`, + Description: `Which type of token to create: 'client' or 'management'. If +a 'management' token, the "policy", "policies", and "consul_roles" parameters are not +required. Defaults to 'client'.`, }, "ttl": { @@ -68,7 +74,7 @@ Defaults to 'client'.`, "lease": { Type: framework.TypeDurationSecond, - Description: "Use ttl instead.", + Description: `Use "ttl" instead.`, Deprecated: true, }, @@ -113,34 +119,36 @@ func (b *backend) pathRolesRead(ctx context.Context, req *logical.Request, d *fr return nil, nil } - var result roleConfig - if err := entry.DecodeJSON(&result); err != nil { + var roleConfigData roleConfig + if err := entry.DecodeJSON(&roleConfigData); err != nil { return nil, err } - if result.TokenType == "" { - result.TokenType = "client" + if roleConfigData.TokenType == "" { + roleConfigData.TokenType = "client" } // Generate the response resp := &logical.Response{ Data: map[string]interface{}{ - "lease": int64(result.TTL.Seconds()), - "ttl": int64(result.TTL.Seconds()), - "max_ttl": int64(result.MaxTTL.Seconds()), - "token_type": result.TokenType, - "local": result.Local, - "consul_namespace": result.ConsulNamespace, - "partition": result.Partition, + "lease": int64(roleConfigData.TTL.Seconds()), + "ttl": int64(roleConfigData.TTL.Seconds()), + "max_ttl": int64(roleConfigData.MaxTTL.Seconds()), + "token_type": roleConfigData.TokenType, + "local": roleConfigData.Local, + "consul_namespace": roleConfigData.ConsulNamespace, + "partition": roleConfigData.Partition, }, } - if result.Policy != "" { - resp.Data["policy"] = base64.StdEncoding.EncodeToString([]byte(result.Policy)) + if roleConfigData.Policy != "" { + resp.Data["policy"] = base64.StdEncoding.EncodeToString([]byte(roleConfigData.Policy)) } - if len(result.Policies) > 0 { - resp.Data["policies"] = result.Policies + if len(roleConfigData.Policies) > 0 { + resp.Data["policies"] = roleConfigData.Policies + } + if len(roleConfigData.ConsulRoles) > 0 { + resp.Data["consul_roles"] = roleConfigData.ConsulRoles } - return resp, nil } @@ -148,18 +156,18 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f tokenType := d.Get("token_type").(string) policy := d.Get("policy").(string) policies := d.Get("policies").([]string) - if len(policies) == 0 { - switch tokenType { - case "client": - if policy == "" { - return logical.ErrorResponse( - "Use either a policy document, or a list of policies, depending on your Consul version"), nil - } - case "management": - default: + roles := d.Get("consul_roles").([]string) + + switch tokenType { + case "client": + if policy == "" && len(policies) == 0 && len(roles) == 0 { return logical.ErrorResponse( - "token_type must be \"client\" or \"management\""), nil + "Use either a policy document, a list of policies, or a list of roles, depending on your Consul version"), nil } + case "management": + default: + return logical.ErrorResponse( + "token_type must be \"client\" or \"management\""), nil } policyRaw, err := base64.StdEncoding.DecodeString(policy) @@ -192,6 +200,7 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f entry, err := logical.StorageEntryJSON("policy/"+name, roleConfig{ Policy: string(policyRaw), Policies: policies, + ConsulRoles: roles, TokenType: tokenType, TTL: ttl, MaxTTL: maxTTL, @@ -221,6 +230,7 @@ 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"` diff --git a/builtin/logical/consul/path_token.go b/builtin/logical/consul/path_token.go index bbcf7ec30..f309fa35f 100644 --- a/builtin/logical/consul/path_token.go +++ b/builtin/logical/consul/path_token.go @@ -22,21 +22,6 @@ func pathToken(b *backend) *framework.Path { Type: framework.TypeString, Description: "Name of the role.", }, - - "policies": { - Type: framework.TypeCommaStringSlice, - Description: `List of policies to attach to the token.`, - }, - - "consul_namespace": { - Type: framework.TypeString, - Description: "Namespace to create the token in.", - }, - - "partition": { - Type: framework.TypeString, - Description: "Admin partition to create the token in.", - }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -105,16 +90,23 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr // Create an ACLToken for Consul 1.4 and above policyLinks := []*api.ACLTokenPolicyLink{} - for _, policyName := range roleConfigData.Policies { policyLinks = append(policyLinks, &api.ACLTokenPolicyLink{ Name: policyName, }) } + roleLinks := []*api.ACLTokenRoleLink{} + for _, roleName := range roleConfigData.ConsulRoles { + roleLinks = append(roleLinks, &api.ACLTokenRoleLink{ + Name: roleName, + }) + } + token, _, err := c.ACL().TokenCreate(&api.ACLToken{ Description: tokenName, Policies: policyLinks, + Roles: roleLinks, Local: roleConfigData.Local, Namespace: roleConfigData.ConsulNamespace, Partition: roleConfigData.Partition, diff --git a/changelog/14014.txt b/changelog/14014.txt new file mode 100644 index 000000000..14bdef15e --- /dev/null +++ b/changelog/14014.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/consul: Add support for consul roles. +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 14dae7da8..ca67d6c78 100644 --- a/go.mod +++ b/go.mod @@ -280,7 +280,7 @@ require ( github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/go-slug v0.7.0 // indirect github.com/hashicorp/go-tfe v0.20.0 // indirect - github.com/hashicorp/go-version v1.3.0 // indirect + github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/mdns v1.0.4 // indirect diff --git a/go.sum b/go.sum index dc98c60a2..643f2477b 100644 --- a/go.sum +++ b/go.sum @@ -897,6 +897,8 @@ github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= +github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= diff --git a/helper/testhelpers/consul/consulhelper.go b/helper/testhelpers/consul/consulhelper.go index a322f9840..77aad05b0 100644 --- a/helper/testhelpers/consul/consulhelper.go +++ b/helper/testhelpers/consul/consulhelper.go @@ -7,6 +7,7 @@ import ( "testing" consulapi "github.com/hashicorp/consul/api" + goversion "github.com/hashicorp/go-version" "github.com/hashicorp/vault/helper/testhelpers/docker" ) @@ -43,7 +44,7 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func if consulVersion != "" { version = consulVersion } else { - version = "1.11.2" // Latest Consul version, update as new releases come out + version = "1.11.3" // Latest Consul version, update as new releases come out } } if strings.HasPrefix(version, "1.3") { @@ -136,55 +137,78 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func return nil, err } + // Create a Consul role that contains the test policy, for Consul 1.5 and newer + currVersion, _ := goversion.NewVersion(version) + roleVersion, _ := goversion.NewVersion("1.5") + if currVersion.GreaterThanOrEqual(roleVersion) { + ACLList := []*consulapi.ACLLink{{Name: "test"}} + + role := &consulapi.ACLRole{ + Name: "role-test", + Description: "consul roles test", + Policies: ACLList, + } + + _, _, err = consul.ACL().RoleCreate(role, q) + if err != nil { + return nil, err + } + } + // Configure a namespace and parition if testing enterprise Consul if isEnterprise { - // Namespaces require Consul 1.7 or newer - namespace := &consulapi.Namespace{ - Name: "ns1", - Description: "ns1 test", - } + namespaceVersion, _ := goversion.NewVersion("1.7") + if currVersion.GreaterThanOrEqual(namespaceVersion) { + namespace := &consulapi.Namespace{ + Name: "ns1", + Description: "ns1 test", + } - _, _, err = consul.Namespaces().Create(namespace, q) - if err != nil { - return nil, err - } + _, _, err = consul.Namespaces().Create(namespace, q) + if err != nil { + return nil, err + } - nsPolicy := &consulapi.ACLPolicy{ - Name: "ns-test", - Description: "namespace test", - Namespace: "ns1", - Rules: `service_prefix "" { + nsPolicy := &consulapi.ACLPolicy{ + Name: "ns-test", + Description: "namespace test", + Namespace: "ns1", + Rules: `service_prefix "" { policy = "read" }`, - } - _, _, err = consul.ACL().PolicyCreate(nsPolicy, q) - if err != nil { - return nil, err + } + _, _, err = consul.ACL().PolicyCreate(nsPolicy, q) + if err != nil { + return nil, err + } } // Partitions require Consul 1.11 or newer - partition := &consulapi.Partition{ - Name: "part1", - Description: "part1 test", - } + partitionVersion, _ := goversion.NewVersion("1.11") + if currVersion.GreaterThanOrEqual(partitionVersion) { + partition := &consulapi.Partition{ + Name: "part1", + Description: "part1 test", + } - _, _, err = consul.Partitions().Create(ctx, partition, q) - if err != nil { - return nil, err - } + _, _, err = consul.Partitions().Create(ctx, partition, q) + if err != nil { + return nil, err + } - partPolicy := &consulapi.ACLPolicy{ - Name: "part-test", - Description: "partition test", - Partition: "part1", - Rules: `service_prefix "" { + partPolicy := &consulapi.ACLPolicy{ + Name: "part-test", + Description: "partition test", + Partition: "part1", + Rules: `service_prefix "" { policy = "read" }`, - } - _, _, err = consul.ACL().PolicyCreate(partPolicy, q) - if err != nil { - return nil, err + } + _, _, err = consul.ACL().PolicyCreate(partPolicy, q) + if err != nil { + return nil, err + } } } diff --git a/website/content/api-docs/secret/consul.mdx b/website/content/api-docs/secret/consul.mdx index 72418f051..bf33cb0ed 100644 --- a/website/content/api-docs/secret/consul.mdx +++ b/website/content/api-docs/secret/consul.mdx @@ -79,15 +79,20 @@ updated attributes. which to create this Consul credential. This is part of the request URL. - `token_type` `(string: "client")` - Specifies the type of token to create when - using this role. Valid values are `"client"` or `"management"`. + using this role. Valid values are `"client"` or `"management"`. If a `"management"` + token, the `policy`, `policies`, and `consul_roles` parameters are not required. + Defaults to `"client`". -- `policy` `(string: )` – Specifies the base64 encoded ACL policy. The - ACL format can be found in the [Consul ACL - documentation](https://www.consul.io/docs/internals/acl). This is - required unless the `token_type` is `management`. +- `policy` `(string: )` – Specifies the base64-encoded ACL policy. This is + required unless the `token_type` is `"management"`. [Deprecated as of Consul 1.4 and + removed as of Consul 1.11.](https://www.consul.io/api/acl/legacy) -- `policies` `(list: )` – The list of policies to assign to the generated - token. This is only available in Consul 1.4 and greater. +- `policies` `(list: )` – The list of policies to assign to the + generated token. Either `policies` or `consul_roles` are required for Consul 1.5 and + above, or just `policies` if using Consul 1.4. + +- `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. - `local` `(bool: false)` - Indicates that the token should not be replicated globally and instead be local to the current datacenter. Only available in Consul @@ -111,11 +116,19 @@ To create management tokens: } ``` -To create a client token with a custom policy: +To create a client token with defined policies: ```json { - "policy": "abd2...==" + "policies": "global-management,policy-2" +} +``` + +To create a client token with defined roles: + +```json +{ + "consul_roles": "role-a,role-b" } ``` @@ -135,25 +148,29 @@ $ curl \ as a string duration with a time suffix like `"30s"` or `"1h"`. If not provided, the default Vault lease is used. -- `policies` `(string: )` – Comma separated list of policies to be applied - to the tokens. +- `policy` `(string: )` – Specifies the base64-encoded ACL policy. The + ACL format can be found in the [Consul ACL + documentation](https://www.consul.io/docs/security/acl/acl-legacy). This is + required unless the `token_type` is `"management"`. ### Sample payload +To create a client token with a custom base64-encoded policy: + ```json { - "policies": "global-management" + "policy": "a2V5ICIi...==" } ``` ### Sample request -```sh -curl \ -→ --request POST \ -→ --header "X-Vault-Token: ..."\ -→ --data @payload.json \ -→ http://127.0.0.1:8200/v1/consul/roles/example-role +```shell-session +$ curl \ + --request POST \ + --header "X-Vault-Token: ..." \ + --data @payload.json \ + http://127.0.0.1:8200/v1/consul/roles/example-role ``` ## Read Role diff --git a/website/content/docs/secrets/consul.mdx b/website/content/docs/secrets/consul.mdx index 90c724e26..48d132567 100644 --- a/website/content/docs/secrets/consul.mdx +++ b/website/content/docs/secrets/consul.mdx @@ -29,7 +29,7 @@ management tool. `acl_master_token` from your Consul configuration file or another management token: - ```sh + ```shell-session $ curl \ --header "X-Consul-Token: my-management-token" \ --request PUT \ @@ -48,7 +48,7 @@ management tool. For Consul 1.4 and above, use the command line to generate a token with the appropriate policy: - ```sh + ```text $ CONSUL_HTTP_TOKEN=d54fe46a-1f57-a589-3583-6b78e334b03b consul acl token create -policy-name=global-management AccessorID: 865dc5e9-e585-3180-7b49-4ddc0fc45135 SecretID: ef35f0f1-885b-0cab-573c-7c91b65a7a7e @@ -70,15 +70,16 @@ management tool. 4. 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. - When users generate credentials, they are generated against this role. For Consul versions below 1.4: + 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 [documented by Consul](https://www.consul.io/docs/security/acl/acl-legacy). + Write a policy and proceed to link it to the role: ```text $ vault write consul/roles/my-role policy=$(base64 <<< 'key "" { policy = "read" }') Success! Data written to: consul/roles/my-role ``` - The policy must be base64-encoded. The policy language is [documented by Consul](https://www.consul.io/docs/internals/acl.html). - For Consul versions 1.4 and above, [generate a policy in Consul](https://www.consul.io/docs/guides/acl.html), and proceed to link it to the role: ```text @@ -86,6 +87,13 @@ 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: + + ```text + $ vault write consul/roles/my-role consul_roles=api-server + 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 suffix like "30s" or "1h".