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
This commit is contained in:
Tim Gross
2024-01-03 08:24:38 -05:00
committed by GitHub
parent 91cba75f5c
commit f2630add91
3 changed files with 58 additions and 0 deletions

3
.changelog/19578.txt Normal file
View File

@@ -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
```

View File

@@ -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
}

View File

@@ -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)