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