e2e: Add Client Identity get and renew tests. (#26632)

This commit is contained in:
James Rasell
2025-08-26 13:49:06 +01:00
committed by GitHub
parent 3fff1aa3cc
commit d0ffb31fea
2 changed files with 151 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package clientidentity
import (
"context"
"fmt"
"sync"
"testing"
"time"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/e2e/e2eutil"
"github.com/shoenig/test/must"
)
func TestClientIdentity(t *testing.T) {
nomad := e2eutil.NomadClient(t)
e2eutil.WaitForLeader(t, nomad)
e2eutil.WaitForNodesReady(t, nomad, 1)
t.Run("testClientIdentity", testClientIdentity)
}
func testClientIdentity(t *testing.T) {
nomad := e2eutil.NomadClient(t)
// Get the list of regions which should include a single entry, so we can
// use it to validate the node identity claims.
regionList, err := nomad.Regions().List()
must.NoError(t, err)
must.Len(t, 1, regionList)
nodeList, _, err := nomad.Nodes().List(nil)
must.NoError(t, err)
must.Greater(t, 0, len(nodeList))
// Create a context with a timeout to avoid waiting indefinitely for the
// client to renew its identity. 40 seconds should be more than enough no
// matter how long each client has until its next heartbeat.
ctx, cancel := context.WithTimeout(t.Context(), 40*time.Second)
defer cancel()
// Use a wait group which allows us to trigger the identity renewal for all
// nodes in parallel, speeding up the test completion time.
wg := new(sync.WaitGroup)
for _, node := range nodeList {
// Perform an initial identity get request and validate the claims
// before asking the client to renew its identity.
nodeIdentityResp, err := nomad.Nodes().Identity().Get(
&api.NodeIdentityGetRequest{
NodeID: node.ID,
},
nil)
must.NoError(t, err)
must.MapNotEmpty(t, nodeIdentityResp.Claims)
assertNodeIdentityClaims(t, regionList[0], node, nodeIdentityResp.Claims)
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
testClientIdentityRenew(t, ctx, nomad, node, nodeIdentityResp.Claims["jti"].(string), regionList[0])
}(wg)
}
// Wait for all the go routines to complete.
wg.Wait()
}
func testClientIdentityRenew(
t *testing.T,
ctx context.Context,
client *api.Client,
nodeStub *api.NodeListStub,
jwtID, region string) {
renewResp, err := client.Nodes().Identity().Renew(
&api.NodeIdentityRenewRequest{NodeID: nodeStub.ID},
nil,
)
must.NoError(t, err)
must.NotNil(t, renewResp)
// Use a ticker, so we can poll the client to view it's identity claims
// until it has renewed.
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
// Wait for the client to renew its identity, or the context to timeout. If
// the context times out then the test will fail and indicate the node did
// not renew its identity within the expected time.
for {
select {
case <-ctx.Done():
t.Fatalf("timeout reached waiting for client %q to renew identity", nodeStub.ID)
return
case <-ticker.C:
nodeIdentityResp, err := client.Nodes().Identity().Get(
&api.NodeIdentityGetRequest{NodeID: nodeStub.ID},
nil)
must.NoError(t, err)
must.MapNotEmpty(t, nodeIdentityResp.Claims)
// If the jwtID has not changed, then continue to wait until the
// client performs its heartbeat to refresh the identity.
if nodeIdentityResp.Claims["jti"].(string) == jwtID {
continue
}
assertNodeIdentityClaims(t, region, nodeStub, nodeIdentityResp.Claims)
return
}
}
}
func assertNodeIdentityClaims(t *testing.T, region string, node *api.NodeListStub, claims map[string]any) {
// Assert the Nomad node specific claims.
must.Eq(t, node.ID, claims["nomad_node_id"].(string))
must.Eq(t, node.Datacenter, claims["nomad_node_datacenter"].(string))
must.Eq(t, node.NodePool, claims["nomad_node_pool"].(string))
// Check the Nomad specific generic claims.
must.Eq(t, "nomadproject.io", claims["aud"].(string))
must.Eq(t, fmt.Sprintf("node:%s:%s:%s:default", region, node.NodePool, node.ID), claims["sub"].(string))
// Check the standard claims that should be present. It's tricky to perform
// exact matches on these as they are time based or generated values, so we
// just check they are present and of the correct type.
must.NotEq(t, "", claims["jti"].(string))
must.Greater(t, 0, claims["iat"].(float64))
must.Greater(t, 0, claims["nbf"].(float64))
must.Greater(t, 0, claims["exp"].(float64))
}

View File

@@ -0,0 +1,10 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
// Package clientidentity provides end-to-end tests for Nomad's client identity
// feature. This does not involve running jobs, but instead focuses on the
// identity API to query and force renewals of client identity claims.
//
// In order to run this test suite only, from the e2e directory you can trigger
// go test -v -run '^TestClientIdentity$' ./client_identity
package clientidentity