hashicorp / hashicorp/consul

binding-rule selectors are not evaluating correctly

Open
#22,510 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
30.1k
Forks
4.6k
Avg merge
1d 18h
Merged PRs (30d)
39

Description

#### Overview of the Issue

Binding rules are being applied regardless of the selector expression.
In some cases they are not applying where they should be.

---

#### Reproduction Steps

Here are some steps I've used to reproduce this minimally.

1. Start a consul cluster
```sh
docker run -d --name test-consul \
-e CONSUL_LOCAL_CONFIG='{"datacenter":"test", "server":true, "enable_debug":true, "acl": {"enabled":true} }' \
hashicorp/consul agent -server -bootstrap-expect=1
```

2. Get a shell
```sh
docker exec -it test-consul sh
```

3. Bootstrap ACL and install three binding rules using different selectors
```sh
consul acl bootstrap

MANAGEMENT_TOKEN=4e35e0b2-60d6-d1cd-1d83-e7e8eebf89b7

consul acl auth-method create -name gce-jwt -type jwt -config '{
"BoundIssuer": "https://accounts.google.com",
"BoundAudiences": ["dummy"],
"JWKSURL": "https://www.googleapis.com/oauth2/v3/certs",
"ClaimMappings": {
"google.compute_engine.instance_name": "instance_name"
}
}' -token $MANAGEMENT_TOKEN

cat <<-EOL | consul acl policy create -name test-policy -token $MANAGEMENT_TOKEN -rules -
key_prefix "" {
policy = "deny"
}
EOL

consul acl role create -name="non-match-role" -policy-name="test-policy" -token $MANAGEMENT_TOKEN

consul acl role create -name="match-role" -policy-name="test-policy" -token $MANAGEMENT_TOKEN

consul acl role create -name="eq-role" -policy-name="test-policy" -token $MANAGEMENT_TOKEN

consul acl binding-rule create -method gce-jwt -bind-type role -bind-name non-match-role \
-selector 'value.instance_name matches "^db-foobar-[0-9]+$"' -token $MANAGEMENT_TOKEN

consul acl binding-rule create -method gce-jwt -bind-type role -bind-name match-role \
-selector 'value.instance_name matches "^db-devops-[0-9]+$"' -token $MANAGEMENT_TOKEN

consul acl binding-rule create -method gce-jwt -bind-type role -bind-name eq-role \
-selector 'value.instance_name == "db-devops-1"' -token $MANAGEMENT_TOKEN
```

4. Login using the JWT
I'm using a JWT token generated form a real GCE instance with the following data:
```json
{
"aud": "dummy",
"azp": "106561459854436270623",
"email": "sa-db-devops@.iam.gserviceaccount.com",
"email_verified": true,
"exp": 1753367383,
"google": {
"compute_engine": {
"instance_creation_timestamp": 1753288965,
"instance_id": "",
"instance_name": "db-devops-1",
"project_id": "",
"project_number": "",
"zone": ""
}
},
"iat": 1753363783,
"iss": "https://accounts.google.com",
"sub": ""
}
```

```sh
consul login -type=jwt -method=gce-jwt -bearer-token-file=/tmp/jwt -token-sink-file=/tmp/token
```

5. Now when we check the token, we see that it's been bound to roles I wouldn't expect it to:
```sh
/ # consul acl token read -self -token=$(cat /tmp/token)
AccessorID: a94844b8-5131-40e7-05c8-835577d9735c
SecretID: 81426925-099b-9870-6528-bc2c66008406
Description: token created via login
Local: true
Auth Method: gce-jwt (Namespace: )
Create Time: 2025-07-24 13:32:01.076757206 +0000 UTC
Roles:
3e93c456-5059-b9cb-b40a-201d4ee37b62 - non-match-role
e5a6d11e-4386-754f-8d99-0b99a66c9c4b - match-role
```

The `non-match-role` is matched when it shouldn't be.
The `match-role` is matched as expected.
The `eq-role` is not matched when it should be.

### Consul info for both Client and Server

Client info

```
/ # consul info
agent:
check_monitors = 0
check_ttls = 0
checks = 0
services = 0
build:
prerelease =
revision = 24bf2b62
version = 1.21.3
version_metadata =
consul:
acl = enabled
bootstrap = true
known_datacenters = 1
leader = true
leader_addr = 172.17.0.3:8300
server = true
raft:
applied_index = 130
commit_index = 130
fsm_pending = 0
last_contact = 0
last_log_index = 130
last_log_term = 2
last_snapshot_index = 0
last_snapshot_term = 0
latest_configuration = [{Suffrage:Voter ID:ae7f9719-2e97-de99-ca2b-c585d3e8e36a Address:172.17.0.3:8300}]
latest_configuration_index = 0
num_peers = 0
protocol_version = 3
protocol_version_max = 3
protocol_version_min = 0
snapshot_version_max = 1
snapshot_version_min = 0
state = Leader
term = 2
runtime:
arch = arm64
cpu_count = 2
goroutines = 195
max_procs = 2
os = linux
version = go1.23.10
serf_lan:
coordinate_resets = 0
encrypted = false
event_queue = 1
event_time = 2
failed = 0
health_score = 0
intent_queue = 0
left = 0
member_time = 1
members = 1
query_queue = 0
query_time = 1
serf_wan:
coordinate_resets = 0
encrypted = false
event_queue = 0
event_time = 1
failed = 0
health_score = 0
intent_queue = 0
left = 0
member_time = 1
members = 1
query_queue = 0
query_time = 1
```

```
Client agent HCL config
```

Server info

```
/ # consul info
agent:
check_monitors = 0
check_ttls = 0
checks = 0
services = 0
build:
prerelease =
revision = 24bf2b62
version = 1.21.3
version_metadata =
consul:
acl = enabled
bootstrap = true
known_datacenters = 1
leader = true
leader_addr = 172.17.0.3:8300
server = true
raft:
applied_index = 130
commit_index = 130
fsm_pending = 0
last_contact = 0
last_log_index = 130
last_log_term = 2
last_snapshot_index = 0
last_snapshot_term = 0
latest_configuration = [{Suffrage:Voter ID:ae7f9719-2e97-de99-ca2b-c585d3e8e36a Address:172.17.0.3:8300}]
latest_configuration_index = 0
num_peers = 0
protocol_version = 3
protocol_version_max = 3
protocol_version_min = 0
snapshot_version_max = 1
snapshot_version_min = 0
state = Leader
term = 2
runtime:
arch = arm64
cpu_count = 2
goroutines = 195
max_procs = 2
os = linux
version = go1.23.10
serf_lan:
coordinate_resets = 0
encrypted = false
event_queue = 1
event_time = 2
failed = 0
health_score = 0
intent_queue = 0
left = 0
member_time = 1
members = 1
query_queue = 0
query_time = 1
serf_wan:
coordinate_resets = 0
encrypted = false
event_queue = 0
event_time = 1
failed = 0
health_score = 0
intent_queue = 0
left = 0
member_time = 1
members = 1
query_queue = 0
query_time = 1
```

```
{"datacenter":"test", "server":true, "enable_debug":true, "acl": {"enabled":true} }
```

### Operating system and Environment details

Host: Macbook Pro M1, Squoia 15.5
Running docker using colima
```
⋊> ~/workspace colima version
colima version 0.8.1
git commit: 96598cc5b64e5e9e1e64891642b91edc8ac49d16

runtime: docker
arch: aarch64
client: v28.1.1
server: v27.4.0
⋊> ~/workspace docker version
Client: Docker Engine - Community
Version: 28.1.1
API version: 1.47 (downgraded from 1.49)
Go version: go1.24.2
Git commit: 4eba377327
Built: Fri Apr 18 09:44:47 2025
OS/Arch: darwin/arm64
Context: colima

Server: Docker Engine - Community
Engine:
Version: 27.4.0
API version: 1.47 (minimum version 1.24)
Go version: go1.22.10
Git commit: 92a8393
Built: Sat Dec 7 10:39:01 2024
OS/Arch: linux/arm64
Experimental: false
containerd:
Version: 1.7.24
GitCommit: 88bf19b2105c8b17560993bee28a01ddc2f97182
runc:
Version: 1.2.2
GitCommit: v1.2.2-0-g7cb3632
docker-init:
Version: 0.19.0
GitCommit: de40ad0
```

This occurs in other environments running in GCE on ubuntu VMs, but I can't share the details.
This reproduction should hopefully suffice.

### Log Fragments

https://gist.github.com/d0x2f/801cd7d9e4fec79e7357bbcdde0a9fd0

Contributor guide

Open the contributing guide

Research direction

Reproduce the issue with the Consul 1.21.3 Docker setup and the three ACL binding rules shown. Start by tracing the ACL binding-rule selector evaluation during JWT login, then add regression coverage for the non-matching, matching, and equality selectors. Done means only the expected roles are bound to the resulting token.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, go
Domain
authentication, authorization, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.