Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consul acl role support #14014

Merged
merged 12 commits into from
Feb 17, 2022
58 changes: 34 additions & 24 deletions builtin/logical/consul/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@ 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
roles are required for Consul 1.5 and above, or just policies if using Consul 1.4.`,
},

"roles": {
robmonte marked this conversation as resolved.
Show resolved Hide resolved
Type: framework.TypeCommaStringSlice,
Description: `List of roles to attach to the token. Either policies or
roles are required for Consul 1.5 and above.`,
},

"local": {
Expand All @@ -50,10 +56,8 @@ and instead be local to the current datacenter. Available in Consul 1.4 and abo
"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" parameter is not required. Defaults to 'client'.`,
robmonte marked this conversation as resolved.
Show resolved Hide resolved
},

"ttl": {
Expand Down Expand Up @@ -113,47 +117,51 @@ 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.Roles) > 0 {
resp.Data["roles"] = roleConfigData.Roles
}

return resp, nil
}

func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
tokenType := d.Get("token_type").(string)
policy := d.Get("policy").(string)
policies := d.Get("policies").([]string)
if len(policies) == 0 {
roles := d.Get("roles").([]string)

if len(policies) == 0 && len(roles) == 0 {
robmonte marked this conversation as resolved.
Show resolved Hide resolved
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
"Use either a policy document, a list of policies, or a list of roles, depending on your Consul version"), nil
}
case "management":
default:
Expand Down Expand Up @@ -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,
Roles: roles,
TokenType: tokenType,
TTL: ttl,
MaxTTL: maxTTL,
Expand Down Expand Up @@ -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"`
Roles []string `json:"roles"`
TTL time.Duration `json:"lease"`
MaxTTL time.Duration `json:"max_ttl"`
TokenType string `json:"token_type"`
Expand Down
24 changes: 8 additions & 16 deletions builtin/logical/consul/path_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,6 @@ func pathToken(b *backend) *framework.Path {
Type: framework.TypeString,
Description: "Name of the role.",
},

robmonte marked this conversation as resolved.
Show resolved Hide resolved
"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{
Expand Down Expand Up @@ -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.Roles {
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,
Expand Down
3 changes: 3 additions & 0 deletions changelog/14014.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
secrets/consul: Add support for consul roles.
```
48 changes: 31 additions & 17 deletions website/content/api-docs/secret/consul.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,15 @@ updated attributes.
- `token_type` `(string: "client")` - Specifies the type of token to create when
using this role. Valid values are `"client"` or `"management"`.

- `policy` `(string: <policy or policies>)` – 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: <policy>)` – Specifies the base64-encoded ACL policy. This is
required unless the `token_type` is `"management"`. [Deprecated as of Consul 1.4 and
robmonte marked this conversation as resolved.
Show resolved Hide resolved
removed as of Consul 1.11.](https://www.consul.io/api/acl/legacy)

- `policies` `(list: <policy or policies>)` – The list of policies to assign to the
robmonte marked this conversation as resolved.
Show resolved Hide resolved
generated token. This is only available in Consul 1.4 and greater.

- `policies` `(list: <policy or policies>)` – The list of policies to assign to the generated
token. This is only available in Consul 1.4 and greater.
- `roles` `(list: <role or roles>)` – The list of roles to assign to the generated
token. This is only available in Consul 1.5 and greater.

- `local` `(bool: false)` - Indicates that the token should not be replicated
globally and instead be local to the current datacenter. Only available in Consul
Expand All @@ -111,11 +113,19 @@ To create management tokens:
}
```

To create a client token with a custom policy:
To create a client token with defined policies:

```json
{
"policies": "global-management,policy-2"
}
```

To create a client token with defined roles:

```json
{
"policy": "abd2...=="
"roles": "role-a,role-b"
}
```

Expand All @@ -135,25 +145,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: <required>)` – Comma separated list of policies to be applied
to the tokens.
- `policy` `(string: <policy>)` – 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
Expand Down
18 changes: 13 additions & 5 deletions website/content/docs/secrets/consul.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand All @@ -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
Expand All @@ -70,22 +70,30 @@ 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
$ vault write consul/roles/my-role policies=readonly
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 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=<duration>` to the
command above with "duration" being a string with a time suffix like "30s" or "1h".
Expand Down