Files
nomad/command/agent/node_identity_endpoint_test.go
James Rasell 2abd72d433 http: Fix client identity renew call when node ID is in URI. (#26773)
When calling the client identity renew API, it is possible the
target node ID is provided by either the URI or within the request
body. This change fixes a bug where all calls using a node_id query
parameter would be reject as it failed to decode the empty request
body.

Co-authored-by: Tim Gross <tgross@hashicorp.com>
2025-09-16 15:15:39 +01:00

238 lines
5.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package agent
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/shoenig/test/must"
)
func TestHTTPServer_NodeIdentityGetRequest(t *testing.T) {
ci.Parallel(t)
t.Run("405 invalid method", func(t *testing.T) {
httpTest(t, cb, func(s *TestAgent) {
respW := httptest.NewRecorder()
badMethods := []string{
http.MethodConnect,
http.MethodDelete,
http.MethodHead,
http.MethodOptions,
http.MethodPatch,
http.MethodPost,
http.MethodPut,
http.MethodTrace,
}
for _, method := range badMethods {
req, err := http.NewRequest(method, "/v1/client/identity", nil)
must.NoError(t, err)
_, err = s.Server.NodeIdentityGetRequest(respW, req)
must.ErrorContains(t, err, "Invalid method")
codedErr, ok := err.(HTTPCodedError)
must.True(t, ok)
must.Eq(t, http.StatusMethodNotAllowed, codedErr.Code())
must.Eq(t, ErrInvalidMethod, codedErr.Error())
}
})
})
t.Run("400 query param with unknown node", func(t *testing.T) {
httpTest(t, nil, func(s *TestAgent) {
respW := httptest.NewRecorder()
req, err := http.NewRequest(
http.MethodGet,
"/v1/client/identity?node_id="+uuid.Generate(),
nil,
)
must.NoError(t, err)
_, err = s.Server.NodeIdentityGetRequest(respW, req)
must.ErrorContains(t, err, "Unknown node")
})
})
t.Run("200 ok query param", func(t *testing.T) {
// Enable the client, so we have something to renew.
configFn := func(c *Config) { c.Client.Enabled = true }
httpTest(t, configFn, func(s *TestAgent) {
respW := httptest.NewRecorder()
req, err := http.NewRequest(
http.MethodGet,
"/v1/client/identity?node_id="+s.client.NodeID(),
nil,
)
must.NoError(t, err)
obj, err := s.Server.NodeIdentityGetRequest(respW, req)
must.NoError(t, err)
must.Eq(t, http.StatusOK, respW.Code)
resp, ok := obj.(structs.NodeIdentityGetResp)
must.True(t, ok)
must.MapLen(t, 9, resp.Claims)
must.MapContainsKeys(t, resp.Claims, []string{
"aud",
"exp",
"jti",
"nbf",
"sub",
"iat",
"nomad_node_datacenter",
"nomad_node_id",
"nomad_node_pool",
})
must.MapContainsValues(t, resp.Claims, []any{
"nomadproject.io",
s.client.NodeID(),
s.client.Datacenter(),
s.client.Node().NodePool,
})
})
})
}
func TestHTTPServer_NodeIdentityRenewRequest(t *testing.T) {
ci.Parallel(t)
t.Run("405 invalid method", func(t *testing.T) {
httpTest(t, nil, func(s *TestAgent) {
respW := httptest.NewRecorder()
badMethods := []string{
http.MethodConnect,
http.MethodDelete,
http.MethodGet,
http.MethodHead,
http.MethodOptions,
http.MethodPatch,
http.MethodTrace,
}
for _, method := range badMethods {
req, err := http.NewRequest(method, "/v1/client/identity/renew", nil)
must.NoError(t, err)
_, err = s.Server.NodeIdentityRenewRequest(respW, req)
must.ErrorContains(t, err, "Invalid method")
codedErr, ok := err.(HTTPCodedError)
must.True(t, ok)
must.Eq(t, http.StatusMethodNotAllowed, codedErr.Code())
must.Eq(t, ErrInvalidMethod, codedErr.Error())
}
})
})
t.Run("400 body with unknown node", func(t *testing.T) {
httpTest(t, nil, func(s *TestAgent) {
reqObj := structs.NodeIdentityRenewReq{
NodeID: uuid.Generate(),
QueryOptions: structs.QueryOptions{
Region: s.config().Region,
},
}
buf := encodeReq(reqObj)
respW := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodPost, "/v1/client/identity/renew", buf)
must.NoError(t, err)
_, err = s.Server.NodeIdentityRenewRequest(respW, req)
must.ErrorContains(t, err, "Unknown node")
})
})
t.Run("400 query param with unknown node", func(t *testing.T) {
httpTest(t, nil, func(s *TestAgent) {
respW := httptest.NewRecorder()
req, err := http.NewRequest(
http.MethodPost,
"/v1/client/identity/renew?node_id="+uuid.Generate(),
nil,
)
must.NoError(t, err)
_, err = s.Server.NodeIdentityRenewRequest(respW, req)
must.ErrorContains(t, err, "Unknown node")
})
})
t.Run("200 ok body", func(t *testing.T) {
// Enable the client, so we have something to renew.
configFn := func(c *Config) { c.Client.Enabled = true }
httpTest(t, configFn, func(s *TestAgent) {
testutil.WaitForClient(t, s.RPC, s.client.NodeID(), s.config().Region)
reqObj := structs.NodeIdentityRenewReq{NodeID: s.client.NodeID()}
buf := encodeReq(reqObj)
respW := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodPost, "/v1/client/identity/renew", buf)
must.NoError(t, err)
obj, err := s.Server.NodeIdentityRenewRequest(respW, req)
must.NoError(t, err)
_, ok := obj.(structs.NodeIdentityRenewResp)
must.True(t, ok)
})
})
t.Run("200 ok query param", func(t *testing.T) {
// Enable the client, so we have something to renew.
configFn := func(c *Config) { c.Client.Enabled = true }
httpTest(t, configFn, func(s *TestAgent) {
testutil.WaitForClient(t, s.RPC, s.client.NodeID(), s.config().Region)
respW := httptest.NewRecorder()
req, err := http.NewRequest(
http.MethodPost,
"/v1/client/identity/renew?node_id="+s.client.NodeID(),
nil,
)
must.NoError(t, err)
obj, err := s.Server.NodeIdentityRenewRequest(respW, req)
must.NoError(t, err)
_, ok := obj.(structs.NodeIdentityRenewResp)
must.True(t, ok)
})
})
}