diff --git a/nomad/node_endpoint.go b/nomad/node_endpoint.go index a62c68d1b..dd15166e1 100644 --- a/nomad/node_endpoint.go +++ b/nomad/node_endpoint.go @@ -209,6 +209,13 @@ func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.No } defer metrics.MeasureSince([]string{"nomad", "client", "deregister"}, time.Now()) + // Check node permissions + if aclObj, err := n.srv.ResolveToken(args.AuthToken); err != nil { + return err + } else if aclObj != nil && !aclObj.AllowNodeWrite() { + return structs.ErrPermissionDenied + } + // Verify the arguments if args.NodeID == "" { return fmt.Errorf("missing node ID for client deregistration") @@ -246,7 +253,7 @@ func (n *Node) Deregister(args *structs.NodeDeregisterRequest, reply *structs.No } // Determine if there are any Vault accessors on the node - accessors, err := n.srv.State().VaultAccessorsByNode(ws, args.NodeID) + accessors, err := snap.VaultAccessorsByNode(ws, args.NodeID) if err != nil { n.srv.logger.Printf("[ERR] nomad.client: looking up accessors for node %q failed: %v", args.NodeID, err) return err diff --git a/nomad/node_endpoint_test.go b/nomad/node_endpoint_test.go index 772a40e3e..5d156f802 100644 --- a/nomad/node_endpoint_test.go +++ b/nomad/node_endpoint_test.go @@ -132,6 +132,71 @@ func TestClientEndpoint_Deregister(t *testing.T) { } } +func TestClientEndpoint_Deregister_ACL(t *testing.T) { + t.Parallel() + s1, root := testACLServer(t, nil) + defer s1.Shutdown() + codec := rpcClient(t, s1) + testutil.WaitForLeader(t, s1.RPC) + + // Create the node + node := mock.Node() + node1 := mock.Node() + state := s1.fsm.State() + if err := state.UpsertNode(1, node); err != nil { + t.Fatalf("err: %v", err) + } + if err := state.UpsertNode(2, node1); err != nil { + t.Fatalf("err: %v", err) + } + + // Create the policy and tokens + validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", mock.NodePolicy(acl.PolicyWrite)) + invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", mock.NodePolicy(acl.PolicyRead)) + + // Deregister without any token and expect it to fail + dereg := &structs.NodeDeregisterRequest{ + NodeID: node.ID, + WriteRequest: structs.WriteRequest{Region: "global"}, + } + var resp structs.GenericResponse + if err := msgpackrpc.CallWithCodec(codec, "Node.Deregister", dereg, &resp); err == nil { + t.Fatalf("node de-register succeeded") + } + + // Deregister with a valid token + dereg.AuthToken = validToken.SecretID + if err := msgpackrpc.CallWithCodec(codec, "Node.Deregister", dereg, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + // Check for the node in the FSM + ws := memdb.NewWatchSet() + out, err := state.NodeByID(ws, node.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out != nil { + t.Fatalf("unexpected node") + } + + // Deregister with an invalid token. + dereg1 := &structs.NodeDeregisterRequest{ + NodeID: node1.ID, + WriteRequest: structs.WriteRequest{Region: "global"}, + } + dereg1.AuthToken = invalidToken.SecretID + if err := msgpackrpc.CallWithCodec(codec, "Node.Deregister", dereg1, &resp); err == nil { + t.Fatalf("rpc should not have succeeded") + } + + // Try with a root token + dereg1.AuthToken = root.SecretID + if err := msgpackrpc.CallWithCodec(codec, "Node.Deregister", dereg1, &resp); err != nil { + t.Fatalf("err: %v", err) + } +} + func TestClientEndpoint_Deregister_Vault(t *testing.T) { t.Parallel() s1 := testServer(t, nil)