1package tokenupdate 2 3import ( 4 "encoding/json" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/consul/agent" 9 "github.com/hashicorp/consul/api" 10 "github.com/hashicorp/consul/sdk/testutil/retry" 11 "github.com/hashicorp/consul/testrpc" 12 "github.com/mitchellh/cli" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15) 16 17func TestTokenUpdateCommand_noTabs(t *testing.T) { 18 t.Parallel() 19 20 if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') { 21 t.Fatal("help has tabs") 22 } 23} 24 25func TestTokenUpdateCommand(t *testing.T) { 26 if testing.Short() { 27 t.Skip("too slow for testing.Short") 28 } 29 30 t.Parallel() 31 32 a := agent.NewTestAgent(t, ` 33 primary_datacenter = "dc1" 34 acl { 35 enabled = true 36 tokens { 37 master = "root" 38 } 39 }`) 40 41 defer a.Shutdown() 42 testrpc.WaitForLeader(t, a.RPC, "dc1") 43 44 // Create a policy 45 client := a.Client() 46 47 policy, _, err := client.ACL().PolicyCreate( 48 &api.ACLPolicy{Name: "test-policy"}, 49 &api.WriteOptions{Token: "root"}, 50 ) 51 require.NoError(t, err) 52 53 // create a token 54 token, _, err := client.ACL().TokenCreate( 55 &api.ACLToken{Description: "test"}, 56 &api.WriteOptions{Token: "root"}, 57 ) 58 require.NoError(t, err) 59 60 // create a legacy token 61 // nolint: staticcheck // we have to use the deprecated API to create a legacy token 62 legacyTokenSecretID, _, err := client.ACL().Create(&api.ACLEntry{ 63 Name: "Legacy token", 64 Type: "client", 65 Rules: "service \"test\" { policy = \"write\" }", 66 }, 67 &api.WriteOptions{Token: "root"}, 68 ) 69 require.NoError(t, err) 70 71 // We fetch the legacy token later to give server time to async background 72 // upgrade it. 73 74 run := func(t *testing.T, args []string) *api.ACLToken { 75 ui := cli.NewMockUi() 76 cmd := New(ui) 77 78 code := cmd.Run(append(args, "-format=json")) 79 require.Equal(t, 0, code) 80 require.Empty(t, ui.ErrorWriter.String()) 81 82 var token api.ACLToken 83 require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &token)) 84 return &token 85 } 86 87 // update with node identity 88 t.Run("node-identity", func(t *testing.T) { 89 token := run(t, []string{ 90 "-http-addr=" + a.HTTPAddr(), 91 "-id=" + token.AccessorID, 92 "-token=root", 93 "-node-identity=foo:bar", 94 "-description=test token", 95 }) 96 97 require.Len(t, token.NodeIdentities, 1) 98 require.Equal(t, "foo", token.NodeIdentities[0].NodeName) 99 require.Equal(t, "bar", token.NodeIdentities[0].Datacenter) 100 }) 101 102 t.Run("node-identity-merge", func(t *testing.T) { 103 token := run(t, []string{ 104 "-http-addr=" + a.HTTPAddr(), 105 "-id=" + token.AccessorID, 106 "-token=root", 107 "-node-identity=bar:baz", 108 "-description=test token", 109 "-merge-node-identities", 110 }) 111 112 require.Len(t, token.NodeIdentities, 2) 113 expected := []*api.ACLNodeIdentity{ 114 { 115 NodeName: "foo", 116 Datacenter: "bar", 117 }, 118 { 119 NodeName: "bar", 120 Datacenter: "baz", 121 }, 122 } 123 require.ElementsMatch(t, expected, token.NodeIdentities) 124 }) 125 126 // update with policy by name 127 t.Run("policy-name", func(t *testing.T) { 128 token := run(t, []string{ 129 "-http-addr=" + a.HTTPAddr(), 130 "-id=" + token.AccessorID, 131 "-token=root", 132 "-policy-name=" + policy.Name, 133 "-description=test token", 134 }) 135 136 require.Len(t, token.Policies, 1) 137 }) 138 139 // update with policy by id 140 t.Run("policy-id", func(t *testing.T) { 141 token := run(t, []string{ 142 "-http-addr=" + a.HTTPAddr(), 143 "-id=" + token.AccessorID, 144 "-token=root", 145 "-policy-id=" + policy.ID, 146 "-description=test token", 147 }) 148 149 require.Len(t, token.Policies, 1) 150 }) 151 152 // update with no description shouldn't delete the current description 153 t.Run("merge-description", func(t *testing.T) { 154 token := run(t, []string{ 155 "-http-addr=" + a.HTTPAddr(), 156 "-id=" + token.AccessorID, 157 "-token=root", 158 "-policy-name=" + policy.Name, 159 }) 160 161 require.Equal(t, "test token", token.Description) 162 }) 163 164 // Need legacy token now, hopefully server had time to generate an accessor ID 165 // in the background but wait for it if not. 166 var legacyToken *api.ACLToken 167 retry.Run(t, func(r *retry.R) { 168 // Fetch the legacy token via new API so we can use it's accessor ID 169 legacyToken, _, err = client.ACL().TokenReadSelf( 170 &api.QueryOptions{Token: legacyTokenSecretID}) 171 require.NoError(r, err) 172 require.NotEmpty(r, legacyToken.AccessorID) 173 }) 174 175 // upgrade legacy token should replace rules and leave token in a "new" state! 176 t.Run("legacy-upgrade", func(t *testing.T) { 177 token := run(t, []string{ 178 "-http-addr=" + a.HTTPAddr(), 179 "-id=" + legacyToken.AccessorID, 180 "-token=root", 181 "-policy-name=" + policy.Name, 182 "-upgrade-legacy", 183 }) 184 185 // Description shouldn't change 186 require.Equal(t, "Legacy token", token.Description) 187 require.Len(t, token.Policies, 1) 188 // Rules should now be empty meaning this is no longer a legacy token 189 require.Empty(t, token.Rules) 190 // Secret should not have changes 191 require.Equal(t, legacyToken.SecretID, token.SecretID) 192 }) 193} 194 195func TestTokenUpdateCommand_JSON(t *testing.T) { 196 if testing.Short() { 197 t.Skip("too slow for testing.Short") 198 } 199 200 t.Parallel() 201 assert := assert.New(t) 202 // Alias because we need to access require package in Retry below 203 req := require.New(t) 204 205 a := agent.NewTestAgent(t, ` 206 primary_datacenter = "dc1" 207 acl { 208 enabled = true 209 tokens { 210 master = "root" 211 } 212 }`) 213 214 defer a.Shutdown() 215 testrpc.WaitForLeader(t, a.RPC, "dc1") 216 217 ui := cli.NewMockUi() 218 219 // Create a policy 220 client := a.Client() 221 222 policy, _, err := client.ACL().PolicyCreate( 223 &api.ACLPolicy{Name: "test-policy"}, 224 &api.WriteOptions{Token: "root"}, 225 ) 226 req.NoError(err) 227 228 // create a token 229 token, _, err := client.ACL().TokenCreate( 230 &api.ACLToken{Description: "test"}, 231 &api.WriteOptions{Token: "root"}, 232 ) 233 req.NoError(err) 234 235 t.Run("update with policy by name", func(t *testing.T) { 236 cmd := New(ui) 237 args := []string{ 238 "-http-addr=" + a.HTTPAddr(), 239 "-id=" + token.AccessorID, 240 "-token=root", 241 "-policy-name=" + policy.Name, 242 "-description=test token", 243 "-format=json", 244 } 245 246 code := cmd.Run(args) 247 assert.Equal(code, 0) 248 assert.Empty(ui.ErrorWriter.String()) 249 250 var jsonOutput json.RawMessage 251 err := json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput) 252 require.NoError(t, err, "token unmarshalling error") 253 }) 254} 255