Files
nomad/client/node_identity_endpoint_test.go
James Rasell 3b0b7db1a1 client: Add client identity API, CLI, and RPC workflow. (#26543)
The Nomad clients store their Nomad identity in memory and within
their state store. While active, it is not possible to dump the
state to view the stored identity token, so having a way to view
the current claims while running aids debugging and operations.

This change adds a client identity workflow, allowing operators
to view the current claims of the nodes identity. It does not
return any of the signing key material.
2025-08-19 08:25:51 +01:00

237 lines
6.0 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package client
import (
"testing"
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/nomad"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/shoenig/test/must"
)
func TestNodeIdentity_Get(t *testing.T) {
ci.Parallel(t)
// Create a test ACL server and client and perform our node identity get
// tests against it.
testACLServer, testServerToken, testACLServerCleanup := nomad.TestACLServer(t, nil)
t.Cleanup(func() { testACLServerCleanup() })
testutil.WaitForLeader(t, testACLServer.RPC)
testACLClient, testACLClientCleanup := TestClient(t, func(c *config.Config) {
c.ACLEnabled = true
c.Servers = []string{testACLServer.GetConfig().RPCAddr.String()}
})
t.Cleanup(func() { _ = testACLClientCleanup() })
testutil.WaitForClientStatusWithToken(
t, testACLServer.RPC, testACLClient.NodeID(), testACLClient.Region(),
structs.NodeStatusReady, testServerToken.SecretID,
)
t.Run("acl_denied", func(t *testing.T) {
must.ErrorContains(
t,
testACLClient.ClientRPC(
structs.NodeIdentityGetRPCMethod,
&structs.NodeIdentityGetReq{},
&structs.NodeIdentityGetResp{},
),
structs.ErrPermissionDenied.Error(),
)
})
t.Run("acl_valid", func(t *testing.T) {
aclPolicy := mock.NodePolicy(acl.PolicyRead)
aclToken := mock.CreatePolicyAndToken(t, testACLServer.State(), 10, t.Name(), aclPolicy)
req := structs.NodeIdentityGetReq{
NodeID: testACLClient.NodeID(),
QueryOptions: structs.QueryOptions{
AuthToken: aclToken.SecretID,
},
}
var resp structs.NodeIdentityGetResp
must.NoError(
t,
testACLClient.ClientRPC(
structs.NodeIdentityGetRPCMethod,
&req,
&resp,
),
)
must.MapLen(t, 10, resp.Claims)
must.MapContainsKeys(t, resp.Claims, []string{
"aud",
"exp",
"jti",
"nbf",
"sub",
"iat",
"nomad_node_class",
"nomad_node_datacenter",
"nomad_node_id",
"nomad_node_pool",
})
must.MapContainsValues(t, resp.Claims, []any{
"nomadproject.io",
testACLClient.NodeID(),
testACLClient.Datacenter(),
testACLClient.Node().NodeClass,
testACLClient.Node().NodePool,
})
})
// Create a test non-ACL server and client and perform our node identity get
// tests against it.
testServer, testServerCleanup := nomad.TestServer(t, nil)
t.Cleanup(func() { testServerCleanup() })
testutil.WaitForLeader(t, testServer.RPC)
testClient, testClientCleanup := TestClient(t, func(c *config.Config) {
c.Servers = []string{testServer.GetConfig().RPCAddr.String()}
})
t.Cleanup(func() { _ = testClientCleanup() })
testutil.WaitForClient(t, testServer.RPC, testClient.NodeID(), testClient.Region())
t.Run("non_acl_valid", func(t *testing.T) {
req := structs.NodeIdentityGetReq{
NodeID: testACLClient.NodeID(),
QueryOptions: structs.QueryOptions{},
}
var resp structs.NodeIdentityGetResp
must.NoError(
t,
testClient.ClientRPC(
structs.NodeIdentityGetRPCMethod,
&req,
&resp,
),
)
must.MapLen(t, 10, resp.Claims)
must.MapContainsKeys(t, resp.Claims, []string{
"aud",
"exp",
"jti",
"nbf",
"sub",
"iat",
"nomad_node_class",
"nomad_node_datacenter",
"nomad_node_id",
"nomad_node_pool",
})
must.MapContainsValues(t, resp.Claims, []any{
"nomadproject.io",
testClient.NodeID(),
testClient.Datacenter(),
testClient.Node().NodeClass,
testClient.Node().NodePool,
})
})
}
func TestNodeIdentity_Renew(t *testing.T) {
ci.Parallel(t)
// Create a test ACL server and client and perform our node identity renewal
// tests against it.
testACLServer, testServerToken, testACLServerCleanup := nomad.TestACLServer(t, nil)
t.Cleanup(func() { testACLServerCleanup() })
testutil.WaitForLeader(t, testACLServer.RPC)
testACLClient, testACLClientCleanup := TestClient(t, func(c *config.Config) {
c.ACLEnabled = true
c.Servers = []string{testACLServer.GetConfig().RPCAddr.String()}
})
t.Cleanup(func() { _ = testACLClientCleanup() })
testutil.WaitForClientStatusWithToken(
t, testACLServer.RPC, testACLClient.NodeID(), testACLClient.Region(),
structs.NodeStatusReady, testServerToken.SecretID,
)
t.Run("acl_denied", func(t *testing.T) {
must.ErrorContains(
t,
testACLClient.ClientRPC(
structs.NodeIdentityRenewRPCMethod,
&structs.NodeIdentityRenewReq{},
&structs.NodeIdentityRenewResp{},
),
structs.ErrPermissionDenied.Error(),
)
})
t.Run("acl_valid", func(t *testing.T) {
aclPolicy := mock.NodePolicy(acl.PolicyWrite)
aclToken := mock.CreatePolicyAndToken(t, testACLServer.State(), 10, t.Name(), aclPolicy)
req := structs.NodeIdentityRenewReq{
NodeID: testACLClient.NodeID(),
QueryOptions: structs.QueryOptions{
AuthToken: aclToken.SecretID,
},
}
must.NoError(
t,
testACLClient.ClientRPC(
structs.NodeIdentityRenewRPCMethod,
&req,
&structs.NodeIdentityRenewResp{},
),
)
renewalVal := testACLClient.identityForceRenewal.Load()
must.True(t, renewalVal)
})
// Create a test non-ACL server and client and perform our node identity
// renewal tests against it.
testServer, testServerCleanup := nomad.TestServer(t, nil)
t.Cleanup(func() { testServerCleanup() })
testutil.WaitForLeader(t, testServer.RPC)
testClient, testClientCleanup := TestClient(t, func(c *config.Config) {
c.Servers = []string{testServer.GetConfig().RPCAddr.String()}
})
t.Cleanup(func() { _ = testClientCleanup() })
testutil.WaitForClient(t, testServer.RPC, testClient.NodeID(), testClient.Region())
t.Run("non_acl_valid", func(t *testing.T) {
must.NoError(
t,
testClient.ClientRPC(
structs.NodeIdentityRenewRPCMethod,
&structs.NodeIdentityRenewReq{
NodeID: testClient.NodeID(),
QueryOptions: structs.QueryOptions{},
},
&structs.NodeIdentityRenewResp{},
),
)
renewalVal := testClient.identityForceRenewal.Load()
must.True(t, renewalVal)
})
}