diff --git a/nomad/alloc_endpoint.go b/nomad/alloc_endpoint.go index f53764a79..033a1a010 100644 --- a/nomad/alloc_endpoint.go +++ b/nomad/alloc_endpoint.go @@ -5,6 +5,7 @@ import ( "github.com/armon/go-metrics" "github.com/hashicorp/go-memdb" + multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/nomad/acl" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/structs" @@ -81,7 +82,25 @@ func (a *Alloc) GetAlloc(args *structs.AllocSpecificRequest, // Check namespace read-job permissions if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { - return err + // If ResolveToken had an unexpected error return that + if err != structs.ErrTokenNotFound { + return err + } + + // Attempt to lookup AuthToken as a Node.SecretID since nodes + // call this endpoint and don't have an ACL token. + node, stateErr := a.srv.fsm.State().NodeBySecretID(nil, args.AuthToken) + if stateErr != nil { + // Return the original ResolveToken error with this err + var merr multierror.Error + merr.Errors = append(merr.Errors, err, stateErr) + return merr.ErrorOrNil() + } + + // Not a node or a valid ACL token + if node == nil { + return structs.ErrTokenNotFound + } } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied } diff --git a/nomad/alloc_endpoint_test.go b/nomad/alloc_endpoint_test.go index d1a9cb370..d10a852d0 100644 --- a/nomad/alloc_endpoint_test.go +++ b/nomad/alloc_endpoint_test.go @@ -267,32 +267,55 @@ func TestAllocEndpoint_GetAlloc_ACL(t *testing.T) { invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) - // Lookup the alloc without a token and expect failure get := &structs.AllocSpecificRequest{ AllocID: alloc.ID, QueryOptions: structs.QueryOptions{Region: "global"}, } - var resp structs.SingleAllocResponse - assert.NotNil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") - // Try with a valid token - get.AuthToken = validToken.SecretID - assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") - assert.EqualValues(resp.Index, 1000, "resp.Index") - assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") + // Lookup the alloc without a token and expect failure + { + var resp structs.SingleAllocResponse + err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp) + assert.Equal(structs.ErrPermissionDenied.Error(), err.Error()) + } + + // Try with a valid ACL token + { + get.AuthToken = validToken.SecretID + var resp structs.SingleAllocResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") + assert.EqualValues(resp.Index, 1000, "resp.Index") + assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") + } + + // Try with a valid Node.SecretID + { + node := mock.Node() + assert.Nil(state.UpsertNode(1005, node)) + get.AuthToken = node.SecretID + var resp structs.SingleAllocResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") + assert.EqualValues(resp.Index, 1000, "resp.Index") + assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") + } // Try with a invalid token - get.AuthToken = invalidToken.SecretID - err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp) - assert.NotNil(err, "RPC") - assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) + { + get.AuthToken = invalidToken.SecretID + var resp structs.SingleAllocResponse + err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp) + assert.NotNil(err, "RPC") + assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) + } // Try with a root token - get.AuthToken = root.SecretID - var resp2 structs.SingleAllocResponse - assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp2), "RPC") - assert.EqualValues(resp2.Index, 1000, "resp.Index") - assert.Equal(alloc, resp2.Alloc, "Returned alloc not equal") + { + get.AuthToken = root.SecretID + var resp structs.SingleAllocResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") + assert.EqualValues(resp.Index, 1000, "resp.Index") + assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") + } } func TestAllocEndpoint_GetAlloc_Blocking(t *testing.T) { diff --git a/nomad/node_endpoint.go b/nomad/node_endpoint.go index 8ee0c49e3..a6049fb3a 100644 --- a/nomad/node_endpoint.go +++ b/nomad/node_endpoint.go @@ -507,8 +507,26 @@ func (n *Node) GetNode(args *structs.NodeSpecificRequest, defer metrics.MeasureSince([]string{"nomad", "client", "get_node"}, time.Now()) // Check node read permissions - if aclObj, err := n.srv.ResolveToken(args.SecretID); err != nil { - return err + if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + // If ResolveToken had an unexpected error return that + if err != structs.ErrTokenNotFound { + return err + } + + // Attempt to lookup AuthToken as a Node.SecretID since nodes + // call this endpoint and don't have an ACL token. + node, stateErr := n.srv.fsm.State().NodeBySecretID(nil, args.AuthToken) + if stateErr != nil { + // Return the original ResolveToken error with this err + var merr multierror.Error + merr.Errors = append(merr.Errors, err, stateErr) + return merr.ErrorOrNil() + } + + // Not a node or a valid ACL token + if node == nil { + return structs.ErrTokenNotFound + } } else if aclObj != nil && !aclObj.AllowNodeRead() { return structs.ErrPermissionDenied } diff --git a/nomad/node_endpoint_test.go b/nomad/node_endpoint_test.go index abbc1b2b3..b8508e2a3 100644 --- a/nomad/node_endpoint_test.go +++ b/nomad/node_endpoint_test.go @@ -941,7 +941,15 @@ func TestClientEndpoint_GetNode_ACL(t *testing.T) { } // Try with a valid token - req.SecretID = validToken.SecretID + req.AuthToken = validToken.SecretID + { + var resp structs.SingleNodeResponse + assert.Nil(msgpackrpc.CallWithCodec(codec, "Node.GetNode", req, &resp), "RPC") + assert.Equal(node.ID, resp.Node.ID) + } + + // Try with a Node.SecretID + req.AuthToken = node.SecretID { var resp structs.SingleNodeResponse assert.Nil(msgpackrpc.CallWithCodec(codec, "Node.GetNode", req, &resp), "RPC") @@ -949,7 +957,7 @@ func TestClientEndpoint_GetNode_ACL(t *testing.T) { } // Try with a invalid token - req.SecretID = invalidToken.SecretID + req.AuthToken = invalidToken.SecretID { var resp structs.SingleNodeResponse err := msgpackrpc.CallWithCodec(codec, "Node.GetNode", req, &resp) @@ -958,7 +966,7 @@ func TestClientEndpoint_GetNode_ACL(t *testing.T) { } // Try with a root token - req.SecretID = root.SecretID + req.AuthToken = root.SecretID { var resp structs.SingleNodeResponse assert.Nil(msgpackrpc.CallWithCodec(codec, "Node.GetNode", req, &resp), "RPC")