github.com/marwan-at-work/consul@v1.4.5/agent/acl_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/consul/agent/structs"
    12  	"github.com/hashicorp/consul/testrpc"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  // NOTE: The tests contained herein are designed to test the HTTP API
    17  //       They are not intended to thoroughly test the backing RPC
    18  //       functionality as that will be done with other tests.
    19  
    20  func TestACL_Disabled_Response(t *testing.T) {
    21  	t.Parallel()
    22  	a := NewTestAgent(t, t.Name(), "")
    23  	defer a.Shutdown()
    24  
    25  	type testCase struct {
    26  		name string
    27  		fn   func(resp http.ResponseWriter, req *http.Request) (interface{}, error)
    28  	}
    29  
    30  	tests := []testCase{
    31  		{"ACLBootstrap", a.srv.ACLBootstrap},
    32  		{"ACLReplicationStatus", a.srv.ACLReplicationStatus},
    33  		{"AgentToken", a.srv.AgentToken}, // See TestAgent_Token
    34  		{"ACLRulesTranslate", a.srv.ACLRulesTranslate},
    35  		{"ACLRulesTranslateLegacyToken", a.srv.ACLRulesTranslateLegacyToken},
    36  		{"ACLPolicyList", a.srv.ACLPolicyList},
    37  		{"ACLPolicyCRUD", a.srv.ACLPolicyCRUD},
    38  		{"ACLPolicyCreate", a.srv.ACLPolicyCreate},
    39  		{"ACLTokenList", a.srv.ACLTokenList},
    40  		{"ACLTokenCreate", a.srv.ACLTokenCreate},
    41  		{"ACLTokenSelf", a.srv.ACLTokenSelf},
    42  		{"ACLTokenCRUD", a.srv.ACLTokenCRUD},
    43  	}
    44  	testrpc.WaitForLeader(t, a.RPC, "dc1")
    45  	for _, tt := range tests {
    46  		t.Run(tt.name, func(t *testing.T) {
    47  			req, _ := http.NewRequest("PUT", "/should/not/care", nil)
    48  			resp := httptest.NewRecorder()
    49  			obj, err := tt.fn(resp, req)
    50  			require.NoError(t, err)
    51  			require.Nil(t, obj)
    52  			require.Equal(t, http.StatusUnauthorized, resp.Code)
    53  			require.Contains(t, resp.Body.String(), "ACL support disabled")
    54  		})
    55  	}
    56  }
    57  
    58  func jsonBody(v interface{}) io.Reader {
    59  	body := bytes.NewBuffer(nil)
    60  	enc := json.NewEncoder(body)
    61  	enc.Encode(v)
    62  	return body
    63  }
    64  
    65  func TestACL_Bootstrap(t *testing.T) {
    66  	t.Parallel()
    67  	a := NewTestAgent(t, t.Name(), TestACLConfig()+`
    68        acl_master_token = ""
    69     `)
    70  	defer a.Shutdown()
    71  
    72  	tests := []struct {
    73  		name   string
    74  		method string
    75  		code   int
    76  		token  bool
    77  	}{
    78  		{"bootstrap", "PUT", http.StatusOK, true},
    79  		{"not again", "PUT", http.StatusForbidden, false},
    80  	}
    81  	testrpc.WaitForLeader(t, a.RPC, "dc1")
    82  	for _, tt := range tests {
    83  		t.Run(tt.name, func(t *testing.T) {
    84  			resp := httptest.NewRecorder()
    85  			req, _ := http.NewRequest(tt.method, "/v1/acl/bootstrap", nil)
    86  			out, err := a.srv.ACLBootstrap(resp, req)
    87  			if tt.token && err != nil {
    88  				t.Fatalf("err: %v", err)
    89  			}
    90  			if got, want := resp.Code, tt.code; got != want {
    91  				t.Fatalf("got %d want %d", got, want)
    92  			}
    93  			if tt.token {
    94  				wrap, ok := out.(*aclBootstrapResponse)
    95  				if !ok {
    96  					t.Fatalf("bad: %T", out)
    97  				}
    98  				if len(wrap.ID) != len("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") {
    99  					t.Fatalf("bad: %v", wrap)
   100  				}
   101  				if wrap.ID != wrap.SecretID {
   102  					t.Fatalf("bad: %v", wrap)
   103  				}
   104  			} else {
   105  				if out != nil {
   106  					t.Fatalf("bad: %T", out)
   107  				}
   108  			}
   109  		})
   110  	}
   111  }
   112  
   113  func TestACL_HTTP(t *testing.T) {
   114  	t.Parallel()
   115  	a := NewTestAgent(t, t.Name(), TestACLConfig())
   116  	defer a.Shutdown()
   117  
   118  	testrpc.WaitForLeader(t, a.RPC, "dc1")
   119  
   120  	idMap := make(map[string]string)
   121  	policyMap := make(map[string]*structs.ACLPolicy)
   122  	tokenMap := make(map[string]*structs.ACLToken)
   123  
   124  	// This is all done as a subtest for a couple reasons
   125  	// 1. It uses only 1 test agent and these are
   126  	//    somewhat expensive to bring up and tear down often
   127  	// 2. Instead of having to bring up a new agent and prime
   128  	//    the ACL system with some data before running the test
   129  	//    we can intelligently order these tests so we can still
   130  	//    test everything with less actual operations and do
   131  	//    so in a manner that is less prone to being flaky
   132  	// 3. While this test will be large it should
   133  	t.Run("Policy", func(t *testing.T) {
   134  		t.Run("Create", func(t *testing.T) {
   135  			policyInput := &structs.ACLPolicy{
   136  				Name:        "test",
   137  				Description: "test",
   138  				Rules:       `acl = "read"`,
   139  				Datacenters: []string{"dc1"},
   140  			}
   141  
   142  			req, _ := http.NewRequest("PUT", "/v1/acl/policy?token=root", jsonBody(policyInput))
   143  			resp := httptest.NewRecorder()
   144  			obj, err := a.srv.ACLPolicyCreate(resp, req)
   145  			require.NoError(t, err)
   146  
   147  			policy, ok := obj.(*structs.ACLPolicy)
   148  			require.True(t, ok)
   149  
   150  			// 36 = length of the string form of uuids
   151  			require.Len(t, policy.ID, 36)
   152  			require.Equal(t, policyInput.Name, policy.Name)
   153  			require.Equal(t, policyInput.Description, policy.Description)
   154  			require.Equal(t, policyInput.Rules, policy.Rules)
   155  			require.Equal(t, policyInput.Datacenters, policy.Datacenters)
   156  			require.True(t, policy.CreateIndex > 0)
   157  			require.Equal(t, policy.CreateIndex, policy.ModifyIndex)
   158  			require.NotNil(t, policy.Hash)
   159  			require.NotEqual(t, policy.Hash, []byte{})
   160  
   161  			idMap["policy-test"] = policy.ID
   162  			policyMap[policy.ID] = policy
   163  		})
   164  
   165  		t.Run("Minimal", func(t *testing.T) {
   166  			policyInput := &structs.ACLPolicy{
   167  				Name:  "minimal",
   168  				Rules: `key_prefix "" { policy = "read" }`,
   169  			}
   170  
   171  			req, _ := http.NewRequest("PUT", "/v1/acl/policy?token=root", jsonBody(policyInput))
   172  			resp := httptest.NewRecorder()
   173  			obj, err := a.srv.ACLPolicyCreate(resp, req)
   174  			require.NoError(t, err)
   175  
   176  			policy, ok := obj.(*structs.ACLPolicy)
   177  			require.True(t, ok)
   178  
   179  			// 36 = length of the string form of uuids
   180  			require.Len(t, policy.ID, 36)
   181  			require.Equal(t, policyInput.Name, policy.Name)
   182  			require.Equal(t, policyInput.Description, policy.Description)
   183  			require.Equal(t, policyInput.Rules, policy.Rules)
   184  			require.Equal(t, policyInput.Datacenters, policy.Datacenters)
   185  			require.True(t, policy.CreateIndex > 0)
   186  			require.Equal(t, policy.CreateIndex, policy.ModifyIndex)
   187  			require.NotNil(t, policy.Hash)
   188  			require.NotEqual(t, policy.Hash, []byte{})
   189  
   190  			idMap["policy-minimal"] = policy.ID
   191  			policyMap[policy.ID] = policy
   192  		})
   193  
   194  		t.Run("Name Chars", func(t *testing.T) {
   195  			policyInput := &structs.ACLPolicy{
   196  				Name:  "read-all_nodes-012",
   197  				Rules: `node_prefix "" { policy = "read" }`,
   198  			}
   199  
   200  			req, _ := http.NewRequest("PUT", "/v1/acl/policy?token=root", jsonBody(policyInput))
   201  			resp := httptest.NewRecorder()
   202  			obj, err := a.srv.ACLPolicyCreate(resp, req)
   203  			require.NoError(t, err)
   204  
   205  			policy, ok := obj.(*structs.ACLPolicy)
   206  			require.True(t, ok)
   207  
   208  			// 36 = length of the string form of uuids
   209  			require.Len(t, policy.ID, 36)
   210  			require.Equal(t, policyInput.Name, policy.Name)
   211  			require.Equal(t, policyInput.Description, policy.Description)
   212  			require.Equal(t, policyInput.Rules, policy.Rules)
   213  			require.Equal(t, policyInput.Datacenters, policy.Datacenters)
   214  			require.True(t, policy.CreateIndex > 0)
   215  			require.Equal(t, policy.CreateIndex, policy.ModifyIndex)
   216  			require.NotNil(t, policy.Hash)
   217  			require.NotEqual(t, policy.Hash, []byte{})
   218  
   219  			idMap["policy-read-all-nodes"] = policy.ID
   220  			policyMap[policy.ID] = policy
   221  		})
   222  
   223  		t.Run("Update Name ID Mistmatch", func(t *testing.T) {
   224  			policyInput := &structs.ACLPolicy{
   225  				ID:          "ac7560be-7f11-4d6d-bfcf-15633c2090fd",
   226  				Name:        "read-all-nodes",
   227  				Description: "Can read all node information",
   228  				Rules:       `node_prefix "" { policy = "read" }`,
   229  				Datacenters: []string{"dc1"},
   230  			}
   231  
   232  			req, _ := http.NewRequest("PUT", "/v1/acl/policy/"+idMap["policy-read-all-nodes"]+"?token=root", jsonBody(policyInput))
   233  			resp := httptest.NewRecorder()
   234  			_, err := a.srv.ACLPolicyCRUD(resp, req)
   235  			require.Error(t, err)
   236  			_, ok := err.(BadRequestError)
   237  			require.True(t, ok)
   238  		})
   239  
   240  		t.Run("Policy CRUD Missing ID in URL", func(t *testing.T) {
   241  			req, _ := http.NewRequest("GET", "/v1/acl/policy/?token=root", nil)
   242  			resp := httptest.NewRecorder()
   243  			_, err := a.srv.ACLPolicyCRUD(resp, req)
   244  			require.Error(t, err)
   245  			_, ok := err.(BadRequestError)
   246  			require.True(t, ok)
   247  		})
   248  
   249  		t.Run("Update", func(t *testing.T) {
   250  			policyInput := &structs.ACLPolicy{
   251  				Name:        "read-all-nodes",
   252  				Description: "Can read all node information",
   253  				Rules:       `node_prefix "" { policy = "read" }`,
   254  				Datacenters: []string{"dc1"},
   255  			}
   256  
   257  			req, _ := http.NewRequest("PUT", "/v1/acl/policy/"+idMap["policy-read-all-nodes"]+"?token=root", jsonBody(policyInput))
   258  			resp := httptest.NewRecorder()
   259  			obj, err := a.srv.ACLPolicyCRUD(resp, req)
   260  			require.NoError(t, err)
   261  
   262  			policy, ok := obj.(*structs.ACLPolicy)
   263  			require.True(t, ok)
   264  
   265  			// 36 = length of the string form of uuids
   266  			require.Len(t, policy.ID, 36)
   267  			require.Equal(t, policyInput.Name, policy.Name)
   268  			require.Equal(t, policyInput.Description, policy.Description)
   269  			require.Equal(t, policyInput.Rules, policy.Rules)
   270  			require.Equal(t, policyInput.Datacenters, policy.Datacenters)
   271  			require.True(t, policy.CreateIndex > 0)
   272  			require.True(t, policy.CreateIndex < policy.ModifyIndex)
   273  			require.NotNil(t, policy.Hash)
   274  			require.NotEqual(t, policy.Hash, []byte{})
   275  
   276  			idMap["policy-read-all-nodes"] = policy.ID
   277  			policyMap[policy.ID] = policy
   278  		})
   279  
   280  		t.Run("ID Supplied", func(t *testing.T) {
   281  			policyInput := &structs.ACLPolicy{
   282  				ID:          "12123d01-37f1-47e6-b55b-32328652bd38",
   283  				Name:        "with-id",
   284  				Description: "test",
   285  				Rules:       `acl = "read"`,
   286  				Datacenters: []string{"dc1"},
   287  			}
   288  
   289  			req, _ := http.NewRequest("PUT", "/v1/acl/policy?token=root", jsonBody(policyInput))
   290  			resp := httptest.NewRecorder()
   291  			_, err := a.srv.ACLPolicyCreate(resp, req)
   292  			require.Error(t, err)
   293  			_, ok := err.(BadRequestError)
   294  			require.True(t, ok)
   295  		})
   296  
   297  		t.Run("Invalid payload", func(t *testing.T) {
   298  			body := bytes.NewBuffer(nil)
   299  			body.Write([]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
   300  
   301  			req, _ := http.NewRequest("PUT", "/v1/acl/policy?token=root", body)
   302  			resp := httptest.NewRecorder()
   303  			_, err := a.srv.ACLPolicyCreate(resp, req)
   304  			require.Error(t, err)
   305  			_, ok := err.(BadRequestError)
   306  			require.True(t, ok)
   307  		})
   308  
   309  		t.Run("Delete", func(t *testing.T) {
   310  			req, _ := http.NewRequest("DELETE", "/v1/acl/policy/"+idMap["policy-minimal"]+"?token=root", nil)
   311  			resp := httptest.NewRecorder()
   312  			_, err := a.srv.ACLPolicyCRUD(resp, req)
   313  			require.NoError(t, err)
   314  			delete(policyMap, idMap["policy-minimal"])
   315  			delete(idMap, "policy-minimal")
   316  		})
   317  
   318  		t.Run("List", func(t *testing.T) {
   319  			req, _ := http.NewRequest("GET", "/v1/acl/policies?token=root", nil)
   320  			resp := httptest.NewRecorder()
   321  			raw, err := a.srv.ACLPolicyList(resp, req)
   322  			require.NoError(t, err)
   323  			policies, ok := raw.(structs.ACLPolicyListStubs)
   324  			require.True(t, ok)
   325  
   326  			// 2 we just created + global management
   327  			require.Len(t, policies, 3)
   328  
   329  			for policyID, expected := range policyMap {
   330  				found := false
   331  				for _, actual := range policies {
   332  					if actual.ID == policyID {
   333  						require.Equal(t, expected.Name, actual.Name)
   334  						require.Equal(t, expected.Datacenters, actual.Datacenters)
   335  						require.Equal(t, expected.Hash, actual.Hash)
   336  						require.Equal(t, expected.CreateIndex, actual.CreateIndex)
   337  						require.Equal(t, expected.ModifyIndex, actual.ModifyIndex)
   338  						found = true
   339  						break
   340  					}
   341  				}
   342  
   343  				require.True(t, found)
   344  			}
   345  		})
   346  
   347  		t.Run("Read", func(t *testing.T) {
   348  			req, _ := http.NewRequest("GET", "/v1/acl/policy/"+idMap["policy-read-all-nodes"]+"?token=root", nil)
   349  			resp := httptest.NewRecorder()
   350  			raw, err := a.srv.ACLPolicyCRUD(resp, req)
   351  			require.NoError(t, err)
   352  			policy, ok := raw.(*structs.ACLPolicy)
   353  			require.True(t, ok)
   354  			require.Equal(t, policyMap[idMap["policy-read-all-nodes"]], policy)
   355  		})
   356  	})
   357  
   358  	t.Run("Token", func(t *testing.T) {
   359  		t.Run("Create", func(t *testing.T) {
   360  			tokenInput := &structs.ACLToken{
   361  				Description: "test",
   362  				Policies: []structs.ACLTokenPolicyLink{
   363  					structs.ACLTokenPolicyLink{
   364  						ID:   idMap["policy-test"],
   365  						Name: policyMap[idMap["policy-test"]].Name,
   366  					},
   367  					structs.ACLTokenPolicyLink{
   368  						ID:   idMap["policy-read-all-nodes"],
   369  						Name: policyMap[idMap["policy-read-all-nodes"]].Name,
   370  					},
   371  				},
   372  			}
   373  
   374  			req, _ := http.NewRequest("PUT", "/v1/acl/token?token=root", jsonBody(tokenInput))
   375  			resp := httptest.NewRecorder()
   376  			obj, err := a.srv.ACLTokenCreate(resp, req)
   377  			require.NoError(t, err)
   378  
   379  			token, ok := obj.(*structs.ACLToken)
   380  			require.True(t, ok)
   381  
   382  			// 36 = length of the string form of uuids
   383  			require.Len(t, token.AccessorID, 36)
   384  			require.Len(t, token.SecretID, 36)
   385  			require.Equal(t, tokenInput.Description, token.Description)
   386  			require.Equal(t, tokenInput.Policies, token.Policies)
   387  			require.True(t, token.CreateIndex > 0)
   388  			require.Equal(t, token.CreateIndex, token.ModifyIndex)
   389  			require.NotNil(t, token.Hash)
   390  			require.NotEqual(t, token.Hash, []byte{})
   391  
   392  			idMap["token-test"] = token.AccessorID
   393  			tokenMap[token.AccessorID] = token
   394  		})
   395  		t.Run("Create Local", func(t *testing.T) {
   396  			tokenInput := &structs.ACLToken{
   397  				Description: "local",
   398  				Policies: []structs.ACLTokenPolicyLink{
   399  					structs.ACLTokenPolicyLink{
   400  						ID:   idMap["policy-test"],
   401  						Name: policyMap[idMap["policy-test"]].Name,
   402  					},
   403  					structs.ACLTokenPolicyLink{
   404  						ID:   idMap["policy-read-all-nodes"],
   405  						Name: policyMap[idMap["policy-read-all-nodes"]].Name,
   406  					},
   407  				},
   408  				Local: true,
   409  			}
   410  
   411  			req, _ := http.NewRequest("PUT", "/v1/acl/token?token=root", jsonBody(tokenInput))
   412  			resp := httptest.NewRecorder()
   413  			obj, err := a.srv.ACLTokenCreate(resp, req)
   414  			require.NoError(t, err)
   415  
   416  			token, ok := obj.(*structs.ACLToken)
   417  			require.True(t, ok)
   418  
   419  			// 36 = length of the string form of uuids
   420  			require.Len(t, token.AccessorID, 36)
   421  			require.Len(t, token.SecretID, 36)
   422  			require.Equal(t, tokenInput.Description, token.Description)
   423  			require.Equal(t, tokenInput.Policies, token.Policies)
   424  			require.True(t, token.CreateIndex > 0)
   425  			require.Equal(t, token.CreateIndex, token.ModifyIndex)
   426  			require.NotNil(t, token.Hash)
   427  			require.NotEqual(t, token.Hash, []byte{})
   428  
   429  			idMap["token-local"] = token.AccessorID
   430  			tokenMap[token.AccessorID] = token
   431  		})
   432  		t.Run("Read", func(t *testing.T) {
   433  			expected := tokenMap[idMap["token-test"]]
   434  			req, _ := http.NewRequest("GET", "/v1/acl/token/"+expected.AccessorID+"?token=root", nil)
   435  			resp := httptest.NewRecorder()
   436  			obj, err := a.srv.ACLTokenCRUD(resp, req)
   437  			require.NoError(t, err)
   438  			token, ok := obj.(*structs.ACLToken)
   439  			require.True(t, ok)
   440  			require.Equal(t, expected, token)
   441  		})
   442  		t.Run("Self", func(t *testing.T) {
   443  			expected := tokenMap[idMap["token-test"]]
   444  			req, _ := http.NewRequest("GET", "/v1/acl/token/self?token="+expected.SecretID, nil)
   445  			resp := httptest.NewRecorder()
   446  			obj, err := a.srv.ACLTokenSelf(resp, req)
   447  			require.NoError(t, err)
   448  			token, ok := obj.(*structs.ACLToken)
   449  			require.True(t, ok)
   450  			require.Equal(t, expected, token)
   451  		})
   452  		t.Run("Clone", func(t *testing.T) {
   453  			tokenInput := &structs.ACLToken{
   454  				Description: "cloned token",
   455  			}
   456  
   457  			baseToken := tokenMap[idMap["token-test"]]
   458  
   459  			req, _ := http.NewRequest("PUT", "/v1/acl/token/"+baseToken.AccessorID+"/clone?token=root", jsonBody(tokenInput))
   460  			resp := httptest.NewRecorder()
   461  			obj, err := a.srv.ACLTokenCRUD(resp, req)
   462  			require.NoError(t, err)
   463  			token, ok := obj.(*structs.ACLToken)
   464  			require.True(t, ok)
   465  
   466  			require.NotEqual(t, baseToken.AccessorID, token.AccessorID)
   467  			require.NotEqual(t, baseToken.SecretID, token.SecretID)
   468  			require.Equal(t, tokenInput.Description, token.Description)
   469  			require.Equal(t, baseToken.Policies, token.Policies)
   470  			require.True(t, token.CreateIndex > 0)
   471  			require.Equal(t, token.CreateIndex, token.ModifyIndex)
   472  			require.NotNil(t, token.Hash)
   473  			require.NotEqual(t, token.Hash, []byte{})
   474  
   475  			idMap["token-cloned"] = token.AccessorID
   476  			tokenMap[token.AccessorID] = token
   477  		})
   478  		t.Run("Update", func(t *testing.T) {
   479  			originalToken := tokenMap[idMap["token-cloned"]]
   480  
   481  			// Accessor and Secret will be filled in
   482  			tokenInput := &structs.ACLToken{
   483  				Description: "Better description for this cloned token",
   484  				Policies: []structs.ACLTokenPolicyLink{
   485  					structs.ACLTokenPolicyLink{
   486  						ID:   idMap["policy-read-all-nodes"],
   487  						Name: policyMap[idMap["policy-read-all-nodes"]].Name,
   488  					},
   489  				},
   490  			}
   491  
   492  			req, _ := http.NewRequest("PUT", "/v1/acl/token/"+originalToken.AccessorID+"?token=root", jsonBody(tokenInput))
   493  			resp := httptest.NewRecorder()
   494  			obj, err := a.srv.ACLTokenCRUD(resp, req)
   495  			require.NoError(t, err)
   496  			token, ok := obj.(*structs.ACLToken)
   497  			require.True(t, ok)
   498  
   499  			require.Equal(t, originalToken.AccessorID, token.AccessorID)
   500  			require.Equal(t, originalToken.SecretID, token.SecretID)
   501  			require.Equal(t, tokenInput.Description, token.Description)
   502  			require.Equal(t, tokenInput.Policies, token.Policies)
   503  			require.True(t, token.CreateIndex > 0)
   504  			require.True(t, token.CreateIndex < token.ModifyIndex)
   505  			require.NotNil(t, token.Hash)
   506  			require.NotEqual(t, token.Hash, []byte{})
   507  			require.NotEqual(t, token.Hash, originalToken.Hash)
   508  
   509  			tokenMap[token.AccessorID] = token
   510  		})
   511  
   512  		t.Run("CRUD Missing Token Accessor ID", func(t *testing.T) {
   513  			req, _ := http.NewRequest("GET", "/v1/acl/token/?token=root", nil)
   514  			resp := httptest.NewRecorder()
   515  			obj, err := a.srv.ACLTokenCRUD(resp, req)
   516  			require.Error(t, err)
   517  			require.Nil(t, obj)
   518  			_, ok := err.(BadRequestError)
   519  			require.True(t, ok)
   520  		})
   521  		t.Run("Update Accessor Mismatch", func(t *testing.T) {
   522  			originalToken := tokenMap[idMap["token-cloned"]]
   523  
   524  			// Accessor and Secret will be filled in
   525  			tokenInput := &structs.ACLToken{
   526  				AccessorID:  "e8aeb69a-0ace-42b9-b95f-d1d9eafe1561",
   527  				Description: "Better description for this cloned token",
   528  				Policies: []structs.ACLTokenPolicyLink{
   529  					structs.ACLTokenPolicyLink{
   530  						ID:   idMap["policy-read-all-nodes"],
   531  						Name: policyMap[idMap["policy-read-all-nodes"]].Name,
   532  					},
   533  				},
   534  			}
   535  
   536  			req, _ := http.NewRequest("PUT", "/v1/acl/token/"+originalToken.AccessorID+"?token=root", jsonBody(tokenInput))
   537  			resp := httptest.NewRecorder()
   538  			obj, err := a.srv.ACLTokenCRUD(resp, req)
   539  			require.Error(t, err)
   540  			require.Nil(t, obj)
   541  			_, ok := err.(BadRequestError)
   542  			require.True(t, ok)
   543  		})
   544  		t.Run("Delete", func(t *testing.T) {
   545  			req, _ := http.NewRequest("DELETE", "/v1/acl/token/"+idMap["token-cloned"]+"?token=root", nil)
   546  			resp := httptest.NewRecorder()
   547  			_, err := a.srv.ACLTokenCRUD(resp, req)
   548  			require.NoError(t, err)
   549  			delete(tokenMap, idMap["token-cloned"])
   550  			delete(idMap, "token-cloned")
   551  		})
   552  		t.Run("List", func(t *testing.T) {
   553  			req, _ := http.NewRequest("GET", "/v1/acl/tokens?token=root", nil)
   554  			resp := httptest.NewRecorder()
   555  			raw, err := a.srv.ACLTokenList(resp, req)
   556  			require.NoError(t, err)
   557  			tokens, ok := raw.(structs.ACLTokenListStubs)
   558  			require.True(t, ok)
   559  
   560  			// 3 tokens created but 1 was deleted + master token + anon token
   561  			require.Len(t, tokens, 4)
   562  
   563  			// this loop doesn't verify anything about the master token
   564  			for tokenID, expected := range tokenMap {
   565  				found := false
   566  				for _, actual := range tokens {
   567  					if actual.AccessorID == tokenID {
   568  						require.Equal(t, expected.Description, actual.Description)
   569  						require.Equal(t, expected.Policies, actual.Policies)
   570  						require.Equal(t, expected.Local, actual.Local)
   571  						require.Equal(t, expected.CreateTime, actual.CreateTime)
   572  						require.Equal(t, expected.Hash, actual.Hash)
   573  						require.Equal(t, expected.CreateIndex, actual.CreateIndex)
   574  						require.Equal(t, expected.ModifyIndex, actual.ModifyIndex)
   575  						found = true
   576  						break
   577  					}
   578  				}
   579  				require.True(t, found)
   580  			}
   581  		})
   582  		t.Run("List by Policy", func(t *testing.T) {
   583  			req, _ := http.NewRequest("GET", "/v1/acl/tokens?token=root&policy="+structs.ACLPolicyGlobalManagementID, nil)
   584  			resp := httptest.NewRecorder()
   585  			raw, err := a.srv.ACLTokenList(resp, req)
   586  			require.NoError(t, err)
   587  			tokens, ok := raw.(structs.ACLTokenListStubs)
   588  			require.True(t, ok)
   589  			require.Len(t, tokens, 1)
   590  			token := tokens[0]
   591  			require.Equal(t, "Master Token", token.Description)
   592  			require.Len(t, token.Policies, 1)
   593  			require.Equal(t, structs.ACLPolicyGlobalManagementID, token.Policies[0].ID)
   594  		})
   595  	})
   596  }