From f2630add918b606c4cb43b39e24c15908ee48703 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Wed, 3 Jan 2024 08:24:38 -0500 Subject: [PATCH] acl: remove timestamps from `WhoAmI` response (#19578) In Nomad 1.7 we updated our JWT library to go-jose, but this changed the wire format of the embedded struct we have in the `IdentityClaims` struct that we return as part of the `WhoAmI` RPC response. This wasn't originally intended to be sent over the wire but other changes in Nomad 1.5+ added a caller to the client. The library change causes a deserialization error on Nomad 1.5 and 1.6 clients, which prevents access to Nomad Variables and SD via template blocks. Removed the incompatible fields from the response, which are unused by any current caller. In a future version of Nomad, we'll likely remove the `WhoAmI` callers from the client in lieu of using the public keys the clients have to check auth. Fixes: https://github.com/hashicorp/nomad/issues/19555 --- .changelog/19578.txt | 3 +++ nomad/acl_endpoint.go | 10 +++++++++ nomad/acl_endpoint_test.go | 45 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 .changelog/19578.txt diff --git a/.changelog/19578.txt b/.changelog/19578.txt new file mode 100644 index 000000000..42dacee35 --- /dev/null +++ b/.changelog/19578.txt @@ -0,0 +1,3 @@ +```release-note:bug +acl: Fixed a bug where 1.5 and 1.6 clients could not access Nomad Variables and Services via templates +``` diff --git a/nomad/acl_endpoint.go b/nomad/acl_endpoint.go index bea0dd98c..63e7e0781 100644 --- a/nomad/acl_endpoint.go +++ b/nomad/acl_endpoint.go @@ -2182,6 +2182,16 @@ func (a *ACL) WhoAmI(args *structs.GenericRequest, reply *structs.ACLWhoAmIRespo } reply.Identity = args.GetIdentity() + + // COMPAT: originally these were time.Time objects but switching to go-jose + // changed them to int64 which aren't compatible with Nomad versions + // <1.7. These aren't used by any existing callers of this handler. + if reply.Identity.Claims != nil { + reply.Identity.Claims.Expiry = nil + reply.Identity.Claims.IssuedAt = nil + reply.Identity.Claims.NotBefore = nil + } + return nil } diff --git a/nomad/acl_endpoint_test.go b/nomad/acl_endpoint_test.go index 4dd5b292a..3d5a260b5 100644 --- a/nomad/acl_endpoint_test.go +++ b/nomad/acl_endpoint_test.go @@ -1913,6 +1913,51 @@ func TestACLEndpoint_ResolveToken(t *testing.T) { assert.Nil(t, resp.Token) } +func TestACLEndpoint_WhoAmI(t *testing.T) { + ci.Parallel(t) + + s1, _, cleanupS1 := TestACLServer(t, nil) + t.Cleanup(cleanupS1) + codec := rpcClient(t, s1) + testutil.WaitForKeyring(t, s1.RPC, "global") + + // Create the register request + token := mock.ACLToken() + s1.fsm.State().UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{token}) + + // Lookup via token + get := &structs.GenericRequest{ + QueryOptions: structs.QueryOptions{Region: "global", AuthToken: token.SecretID}, + } + var resp structs.ACLWhoAmIResponse + err := msgpackrpc.CallWithCodec(codec, "ACL.WhoAmI", get, &resp) + must.NoError(t, err) + must.Eq(t, token, resp.Identity.ACLToken) + + // Lookup non-existing token + get.AuthToken = uuid.Generate() + var resp2 structs.ACLWhoAmIResponse + err = msgpackrpc.CallWithCodec(codec, "ACL.WhoAmI", get, &resp2) + must.EqError(t, err, structs.ErrPermissionDenied.Error()) + must.Nil(t, resp2.Identity) + + // Lookup identity claim + alloc := mock.Alloc() + s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 1500, []*structs.Allocation{alloc}) + claims := structs.NewIdentityClaims(alloc.Job, alloc, + wiHandle, // see encrypter_test.go + alloc.LookupTask("web").Identity, time.Now().Add(-10*time.Minute)) + jwtToken, _, err := s1.encrypter.SignClaims(claims) + must.NoError(t, err) + + get.AuthToken = jwtToken + var resp3 structs.ACLWhoAmIResponse + err = msgpackrpc.CallWithCodec(codec, "ACL.WhoAmI", get, &resp3) + must.NoError(t, err) + must.NotNil(t, resp3.Identity.Claims) + must.Eq(t, alloc.ID, resp3.Identity.Claims.AllocationID) +} + func TestACLEndpoint_OneTimeToken(t *testing.T) { ci.Parallel(t)