ACL policies allows multiple rules where there should only be one and uses the last one
- Dominant language
- Go
- Stars
- 30.1k
- Forks
- 4.6k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 43
Description
#### Overview of the Issue
HCLv1 decoding used for ACL policies allows Go objects to be overwritten when there are multiple instances of the HCL object. Any rules where only one should be allowed will not produce any errors, and the last instance of the rule is used. Because, in essence, it is valid HCL, but not valid Consul ACL. The underlying HCLv1 doesn't safely decode the content, and allows the objects to overwrite into the Go object during decoding.
https://github.com/hashicorp/consul/blob/97facc994eb2f619560ea59c51d55677140968a4/acl/policy.go#L69-L87
#### Reproduction Steps
Setup a Consul cluster, in this case using a Terraform module that also deploys Nomad to GCP:
```hcl
variable "project" {
description = "project name to deploy the cluster in"
}
variable "credentials" {
description = "The GCP service account credentials file path to use"
}
module "nomad" {
source = "picatz/nomad/google"
version = "v2.3.0"
project = var.project
credentials = var.credentials
bastion_enabled = false
server_instances = 3
client_instances = 5
}
output "consul_master_token" {
value = module.nomad.consul_master_token
}
```
```console
$ terraform apply -var="project=$GOOGLE_PROJECT" -var="credentials=$GOOGLE_APPLICATION_CREDENTIALS"
...
$ export CONSUL_HTTP_TOKEN="$(terraform output -json | jq -r .consul_master_token.value)"
$ gcloud compute ssh server-0 --tunnel-through-iap -- -f -N -L 127.0.0.1:8500:127.0.0.1:8500
$ consul catalog nodes
Node ID Address DC
client-0 8d4a6702 192.168.2.5 dc1
client-1 50428625 192.168.2.3 dc1
client-2 673d542c 192.168.2.6 dc1
client-3 a46edb7b 192.168.2.7 dc1
client-4 f2a61358 192.168.2.9 dc1
server-0 83617184 192.168.2.8 dc1
server-1 25419128 192.168.2.4 dc1
server-2 1b91571b 192.168.2.2 dc1
```
Now with the cluster is running, bootstrapped, and exposed on `127.0.0.1:8500` via SSH, we can now create ACL policies. Let's consider the following policy with two conflicting operator rules:
```hcl
operator = "read"
operator = "write"
```
```console
$ consul acl policy create -name "example" -rules @rules.hcl
ID: 6c0c42f1-519a-065d-692c-969303cd6736
Name: example
Description:
Datacenters:
Rules:
operator = "read"
operator = "write"
$ consul acl token create -policy-id 6c0c42f1-519a-065d-692c-969303cd6736
AccessorID: e46d4f65-4c3f-19c5-a0ca-c6bc7af9a116
SecretID: 8b3ae5c1-6e53-e449-1ed5-bfd0789cbd0f
Description:
Local: false
Create Time: 2021-01-15 20:27:21.461339861 +0000 UTC
Policies:
6c0c42f1-519a-065d-692c-969303cd6736 - example
$ export CONSUL_HTTP_TOKEN=8b3ae5c1-6e53-e449-1ed5-bfd0789cbd0f
```
Using that newly minted operator token, we can verify we have access to the API, and with a limited set of permissions as we would expect. For example, we are no longer able to read nodes from the catalog, because we didn't grant access and have a default deny policy.
```console
$ consul catalog nodes
No nodes match the given query - try expanding your search.
```
But we can read raft peers, which requires the `operator:read` policy.
```console
$ consul operator raft list-peers
Node ID Address State Voter RaftProtocol
server-2 1b91571b-f1d8-2474-791d-05d288f12c27 192.168.2.2:8300 leader true 3
server-1 25419128-5e16-6cd8-7934-1b3f91a8d917 192.168.2.4:8300 follower true 3
server-0 83617184-15fd-e680-6301-a2dfee45796d 192.168.2.8:8300 follower true 3
```
As well as delete raft peers, which requires the `operator:write` policy.
```console
$ consul operator raft remove-peer -id 1b91571b-f1d8-2474-791d-05d288f12c27
Removed peer with id "1b91571b-f1d8-2474-791d-05d288f12c27"
```
This means the `operator = write` ACL rule is the one that's "sticking". This is because the last rule in the policy dictates the behavior due to usage of HCLv1. If we change the ACL policy around, then `remove-peer` no longer works. Changing back to the root token, we can update the policy to show that.
```hcl
operator = "write"
operator = "read"
```
```console
$ export CONSUL_HTTP_TOKEN="$(terraform output -json | jq -r .consul_master_token.value)"
$ consul acl policy update -id 6c0c42f1-519a-065d-692c-969303cd6736 -rules @rules.hcl
ID: 6c0c42f1-519a-065d-692c-969303cd6736
Name: example
Description:
Datacenters:
Rules:
operator = "write"
operator = "read"
$ export CONSUL_HTTP_TOKEN=8b3ae5c1-6e53-e449-1ed5-bfd0789cbd0f
$ consul operator raft list-peers
Node ID Address State Voter RaftProtocol
server-1 25419128-5e16-6cd8-7934-1b3f91a8d917 192.168.2.4:8300 leader true 3
server-0 83617184-15fd-e680-6301-a2dfee45796d 192.168.2.8:8300 follower true 3
server-2 1b91571b-f1d8-2474-791d-05d288f12c27 192.168.2.2:8300 follower true 3
$ consul operator raft remove-peer -id 1b91571b-f1d8-2474-791d-05d288f12c27
Error removing peer: Unexpected response code: 403 (rpc error making call: Permission denied)
```
Related to https://github.com/hashicorp/nomad/issues/9832
Contributor guide
Assessment
This issue has not been assessed yet.