acl: fix ACL bypass for anon requests that pass thru client HTTP

Requests without an ACL token that pass thru the client's HTTP API are treated
as though they come from the client itself. This allows bypass of ACLs on RPC
requests where ACL permissions are checked (like `Job.Register`). Invalid tokens
are correctly rejected.

Fix the bypass by only setting a client ID on the identity if we have a valid node secret.

Note that this changeset will break rate metrics for RPCs sent by clients
without a client secret such as `Node.GetClientAllocs`; these requests will be
recorded as anonymous.

Future work should:
* Ensure the node secret is sent with all client-driven RPCs except
  `Node.Register` which is TOFU.
* Create a new `acl.ACL` object from client requests so that we
  can enforce ACLs for all endpoints in a uniform way that's less error-prone.~
This commit is contained in:
Tim Gross 2023-03-31 11:40:21 -04:00
parent 9b4871fece
commit 8278f23042
3 changed files with 13 additions and 6 deletions

3
.changelog/16775.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:security
acl: Fixed a bug where unauthenticated HTTP API requests through the client could bypass ACL policy checking [CVE-2023-1782](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1782) [[GH-16775](https://github.com/hashicorp/nomad/issues/16775)]
```

View File

@ -102,12 +102,9 @@ func (s *Server) Authenticate(ctx *RPCContext, args structs.RequestWithIdentity)
// At this point we either have an anonymous token or an invalid one.
// Previously-connected clients will have a NodeID set on the context, which
// is available for all yamux streams over the same yamux session (and TCP
// connection). This will be a large portion of the RPCs sent, but we can't
// fast-path this at the top of the method, because authenticated HTTP
// requests to the clients will come in over to the same session.
if ctx.NodeID != "" {
// TODO(tgross): remove this entirely in 1.6.0 and enforce that all RPCs
// driven by the clients have secret IDs set
if ctx.NodeID != "" && secretID != "" {
args.SetIdentity(&structs.AuthenticatedIdentity{ClientID: ctx.NodeID})
return nil
}

View File

@ -217,6 +217,13 @@ func TestAuthenticate_mTLS(t *testing.T) {
expectClientID: node.ID,
expectIDKey: fmt.Sprintf("client:%s", node.ID),
},
{
name: "from client missing secret", // ex. Node.Register
tlsCfg: clientTLSCfg,
expectAccessor: "anonymous",
expectTLSName: "regionFoo.nomad",
expectIP: follower.GetConfig().RPCAddr.IP.String(),
},
{
name: "from failed workload", // ex. Variables.List
tlsCfg: clientTLSCfg,