rpc: Use client only auth for node get client allocs endpoint. (#26084)

The RPC is only ever called from a Nomad client which means we
can move it away from the generic Authenticate function to the
tighter AuthenticateClientOnly one. An addition check to ensure
the ACL object allows client operations is performed, mimicking
other endpoints of this nature.
This commit is contained in:
James Rasell
2025-06-23 07:44:32 +01:00
committed by GitHub
parent cdde082362
commit d1f77a48ab
2 changed files with 37 additions and 8 deletions

View File

@@ -1101,7 +1101,11 @@ func (n *Node) GetAllocs(args *structs.NodeSpecificRequest,
func (n *Node) GetClientAllocs(args *structs.NodeSpecificRequest,
reply *structs.NodeClientAllocsResponse) error {
authErr := n.srv.Authenticate(n.ctx, args)
// This RPC is only ever called by Nomad clients, so we can use the tightly
// scoped AuthenticateClientOnly method to authenticate and authorize the
// request.
aclObj, authErr := n.srv.AuthenticateClientOnly(n.ctx, args)
isForwarded := args.IsForwarded()
if done, err := n.srv.forward("Node.GetClientAllocs", args, args, reply); done {
// We have a valid node connection since there is no error from the
@@ -1120,6 +1124,10 @@ func (n *Node) GetClientAllocs(args *structs.NodeSpecificRequest,
}
defer metrics.MeasureSince([]string{"nomad", "client", "get_client_allocs"}, time.Now())
if !aclObj.AllowClientOp() {
return structs.ErrPermissionDenied
}
// Verify the arguments
if args.NodeID == "" {
return fmt.Errorf("missing node ID")

View File

@@ -2406,6 +2406,19 @@ func TestClientEndpoint_GetClientAllocs(t *testing.T) {
// Check that we have no client connections
require.Empty(s1.connectedNodes())
// The RPC is client only, so perform a test using the leader ACL token to
// ensure that even this powerful token cannot access the endpoint.
leaderACLReq := structs.NodeSpecificRequest{
NodeID: uuid.Generate(),
QueryOptions: structs.QueryOptions{
Region: "global",
AuthToken: s1.leaderAcl,
},
}
var leaderACLResp structs.NodeClientAllocsResponse
err := msgpackrpc.CallWithCodec(codec, "Node.GetClientAllocs", &leaderACLReq, &leaderACLResp)
must.ErrorContains(t, err, "Permission denied")
// Create the register request
node := mock.Node()
state := s1.fsm.State()
@@ -2415,16 +2428,19 @@ func TestClientEndpoint_GetClientAllocs(t *testing.T) {
alloc := mock.Alloc()
alloc.NodeID = node.ID
state.UpsertJobSummary(99, mock.JobSummary(alloc.JobID))
err := state.UpsertAllocs(structs.MsgTypeTestSetup, 100, []*structs.Allocation{alloc})
err = state.UpsertAllocs(structs.MsgTypeTestSetup, 100, []*structs.Allocation{alloc})
if err != nil {
t.Fatalf("err: %v", err)
}
// Lookup the allocs
get := &structs.NodeSpecificRequest{
NodeID: node.ID,
SecretID: node.SecretID,
QueryOptions: structs.QueryOptions{Region: "global"},
NodeID: node.ID,
SecretID: node.SecretID,
QueryOptions: structs.QueryOptions{
Region: "global",
AuthToken: node.SecretID,
},
}
var resp2 structs.NodeClientAllocsResponse
if err := msgpackrpc.CallWithCodec(codec, "Node.GetClientAllocs", get, &resp2); err != nil {
@@ -2517,6 +2533,7 @@ func TestClientEndpoint_GetClientAllocs_Blocking(t *testing.T) {
NodeID: node.ID,
SecretID: node.SecretID,
QueryOptions: structs.QueryOptions{
AuthToken: node.SecretID,
Region: "global",
MinQueryIndex: 50,
MaxQueryTime: time.Second,
@@ -2635,6 +2652,7 @@ func TestClientEndpoint_GetClientAllocs_Blocking_GC(t *testing.T) {
NodeID: node.ID,
SecretID: node.SecretID,
QueryOptions: structs.QueryOptions{
AuthToken: node.SecretID,
Region: "global",
MinQueryIndex: 50,
MaxQueryTime: time.Second,
@@ -2711,9 +2729,12 @@ func TestClientEndpoint_GetClientAllocs_WithoutMigrateTokens(t *testing.T) {
// Lookup the allocs
get := &structs.NodeSpecificRequest{
NodeID: node.ID,
SecretID: node.SecretID,
QueryOptions: structs.QueryOptions{Region: "global"},
NodeID: node.ID,
SecretID: node.SecretID,
QueryOptions: structs.QueryOptions{
AuthToken: node.SecretID,
Region: "global",
},
}
var resp2 structs.NodeClientAllocsResponse