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

Fix ACL check on health endpoint #17424

Merged
merged 3 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/17424.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:breaking-change
api: The `/v1/health/connect/` and `/v1/health/ingress/` endpoints will now immediately return 403 "Permission Denied" errors whenever a token with insufficent `service:read` permissions is provided. Prior to this change, the endpoints would simply return a success code with an empty result list when this occurred.
```
19 changes: 8 additions & 11 deletions agent/consul/health_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
return err
}

// If we're doing a connect or ingress query, we need read access to the service
// we're trying to find proxies for, so check that.
if args.Connect || args.Ingress {
if authz.ServiceRead(args.ServiceName, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
}

filter, err := bexpr.CreateFilter(args.Filter, nil, reply.Nodes)
if err != nil {
return err
Expand All @@ -247,17 +255,6 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc
return err
}

// If we're doing a connect or ingress query, we need read access to the service
// we're trying to find proxies for, so check that.
if args.Connect || args.Ingress {
// TODO(acl-error-enhancements) Look for ways to percolate this information up to give any feedback to the user.
if authz.ServiceRead(args.ServiceName, &authzContext) != acl.Allow {
// Return the index here so that the agent cache does not infinitely loop.
reply.Index = index
return nil
}
}

resolvedNodes := nodes
if args.MergeCentralConfig {
for _, node := range resolvedNodes {
Expand Down
7 changes: 3 additions & 4 deletions agent/consul/health_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1125,9 +1125,8 @@ node "foo" {
QueryOptions: structs.QueryOptions{Token: token},
}
var resp structs.IndexedCheckServiceNodes
assert.Nil(t, msgpackrpc.CallWithCodec(codec, "Health.ServiceNodes", &req, &resp))
assert.ErrorContains(t, msgpackrpc.CallWithCodec(codec, "Health.ServiceNodes", &req, &resp), "Permission denied")
assert.Len(t, resp.Nodes, 0)
assert.Greater(t, resp.Index, uint64(0))

// List w/ token. This should work since we're requesting "foo", but should
// also only contain the proxies with names that adhere to our ACL.
Expand Down Expand Up @@ -1465,7 +1464,7 @@ func TestHealth_ServiceNodes_Ingress_ACL(t *testing.T) {
ServiceName: "db",
Ingress: true,
}
require.Nil(t, msgpackrpc.CallWithCodec(codec, "Health.ServiceNodes", &req, &out2))
require.ErrorContains(t, msgpackrpc.CallWithCodec(codec, "Health.ServiceNodes", &req, &out2), "Permission denied")
require.Len(t, out2.Nodes, 0)

// Requesting a service that is not covered by the token's policy
Expand All @@ -1475,7 +1474,7 @@ func TestHealth_ServiceNodes_Ingress_ACL(t *testing.T) {
Ingress: true,
QueryOptions: structs.QueryOptions{Token: token.SecretID},
}
require.Nil(t, msgpackrpc.CallWithCodec(codec, "Health.ServiceNodes", &req, &out2))
require.ErrorContains(t, msgpackrpc.CallWithCodec(codec, "Health.ServiceNodes", &req, &out2), "Permission denied")
require.Len(t, out2.Nodes, 0)

// Requesting service covered by the token's policy
Expand Down
14 changes: 14 additions & 0 deletions website/content/docs/upgrading/upgrade-specific.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ upgrade flow.

## Consul 1.16.x

#### API health endpoints return different status code

Consul versions 1.16.0+ will now return an error 403 "Permission denied" status
whenever the `/v1/health/connect/` and `/v1/health/ingress/` endpoints are
queried with insufficient ACL `service:read` privileges.

In Consul versions prior to 1.16.0, the service health API would not explicitly
return an error whenever a token with invalid permissions was given. It would
instead return an empty list with a success status code.

Ensure that all of your applications can handle this new API behavior properly
before upgrading. No change is necessary if you do not have a custom application
that is querying this API directly.

#### Remove deprecated service-defaults peer upstream override behavior

When configuring a service defaults configuration entry, the [`UpstreamConfig.Overrides` configuration](/consul/docs/connect/config-entries/service-defaults#upstreamconfig-overrides)
Expand Down