open-consul/command/acl/token/update/token_update_test.go
Matt Keeler cdc4b20afa
ACL Node Identities (#7970)
A Node Identity is very similar to a service identity. Its main targeted use is to allow creating tokens for use by Consul agents that will grant the necessary permissions for all the typical agent operations (node registration, coordinate updates, anti-entropy).

Half of this commit is for golden file based tests of the acl token and role cli output. Another big updates was to refactor many of the tests in agent/consul/acl_endpoint_test.go to use the same style of tests and the same helpers. Besides being less boiler plate in the tests it also uses a common way of starting a test server with ACLs that should operate without any warnings regarding deprecated non-uuid master tokens etc.
2020-06-16 12:54:27 -04:00

255 lines
6.1 KiB
Go

package tokenupdate
import (
"encoding/json"
"os"
"strings"
"testing"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/hashicorp/consul/testrpc"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTokenUpdateCommand_noTabs(t *testing.T) {
t.Parallel()
if strings.ContainsRune(New(cli.NewMockUi()).Help(), '\t') {
t.Fatal("help has tabs")
}
}
func TestTokenUpdateCommand(t *testing.T) {
t.Parallel()
testDir := testutil.TempDir(t, "acl")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t, `
primary_datacenter = "dc1"
acl {
enabled = true
tokens {
master = "root"
}
}`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Create a policy
client := a.Client()
policy, _, err := client.ACL().PolicyCreate(
&api.ACLPolicy{Name: "test-policy"},
&api.WriteOptions{Token: "root"},
)
require.NoError(t, err)
// create a token
token, _, err := client.ACL().TokenCreate(
&api.ACLToken{Description: "test"},
&api.WriteOptions{Token: "root"},
)
require.NoError(t, err)
// create a legacy token
// nolint: staticcheck // we have to use the deprecated API to create a legacy token
legacyTokenSecretID, _, err := client.ACL().Create(&api.ACLEntry{
Name: "Legacy token",
Type: "client",
Rules: "service \"test\" { policy = \"write\" }",
},
&api.WriteOptions{Token: "root"},
)
require.NoError(t, err)
// We fetch the legacy token later to give server time to async background
// upgrade it.
run := func(t *testing.T, args []string) *api.ACLToken {
ui := cli.NewMockUi()
cmd := New(ui)
code := cmd.Run(append(args, "-format=json"))
require.Equal(t, 0, code)
require.Empty(t, ui.ErrorWriter.String())
var token api.ACLToken
require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &token))
return &token
}
// update with node identity
t.Run("node-identity", func(t *testing.T) {
token := run(t, []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + token.AccessorID,
"-token=root",
"-node-identity=foo:bar",
"-description=test token",
})
require.Len(t, token.NodeIdentities, 1)
require.Equal(t, "foo", token.NodeIdentities[0].NodeName)
require.Equal(t, "bar", token.NodeIdentities[0].Datacenter)
})
t.Run("node-identity-merge", func(t *testing.T) {
token := run(t, []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + token.AccessorID,
"-token=root",
"-node-identity=bar:baz",
"-description=test token",
"-merge-node-identities",
})
require.Len(t, token.NodeIdentities, 2)
expected := []*api.ACLNodeIdentity{
&api.ACLNodeIdentity{
NodeName: "foo",
Datacenter: "bar",
},
&api.ACLNodeIdentity{
NodeName: "bar",
Datacenter: "baz",
},
}
require.ElementsMatch(t, expected, token.NodeIdentities)
})
// update with policy by name
t.Run("policy-name", func(t *testing.T) {
token := run(t, []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + token.AccessorID,
"-token=root",
"-policy-name=" + policy.Name,
"-description=test token",
})
require.Len(t, token.Policies, 1)
})
// update with policy by id
t.Run("policy-id", func(t *testing.T) {
token := run(t, []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + token.AccessorID,
"-token=root",
"-policy-id=" + policy.ID,
"-description=test token",
})
require.Len(t, token.Policies, 1)
})
// update with no description shouldn't delete the current description
t.Run("merge-description", func(t *testing.T) {
token := run(t, []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + token.AccessorID,
"-token=root",
"-policy-name=" + policy.Name,
})
require.Equal(t, "test token", token.Description)
})
// Need legacy token now, hopefully server had time to generate an accessor ID
// in the background but wait for it if not.
var legacyToken *api.ACLToken
retry.Run(t, func(r *retry.R) {
// Fetch the legacy token via new API so we can use it's accessor ID
legacyToken, _, err = client.ACL().TokenReadSelf(
&api.QueryOptions{Token: legacyTokenSecretID})
require.NoError(r, err)
require.NotEmpty(r, legacyToken.AccessorID)
})
// upgrade legacy token should replace rules and leave token in a "new" state!
t.Run("legacy-upgrade", func(t *testing.T) {
token := run(t, []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + legacyToken.AccessorID,
"-token=root",
"-policy-name=" + policy.Name,
"-upgrade-legacy",
})
// Description shouldn't change
require.Equal(t, "Legacy token", token.Description)
require.Len(t, token.Policies, 1)
// Rules should now be empty meaning this is no longer a legacy token
require.Empty(t, token.Rules)
// Secret should not have changes
require.Equal(t, legacyToken.SecretID, token.SecretID)
})
}
func TestTokenUpdateCommand_JSON(t *testing.T) {
t.Parallel()
assert := assert.New(t)
// Alias because we need to access require package in Retry below
req := require.New(t)
testDir := testutil.TempDir(t, "acl")
defer os.RemoveAll(testDir)
a := agent.NewTestAgent(t, `
primary_datacenter = "dc1"
acl {
enabled = true
tokens {
master = "root"
}
}`)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
ui := cli.NewMockUi()
// Create a policy
client := a.Client()
policy, _, err := client.ACL().PolicyCreate(
&api.ACLPolicy{Name: "test-policy"},
&api.WriteOptions{Token: "root"},
)
req.NoError(err)
// create a token
token, _, err := client.ACL().TokenCreate(
&api.ACLToken{Description: "test"},
&api.WriteOptions{Token: "root"},
)
req.NoError(err)
t.Run("update with policy by name", func(t *testing.T) {
cmd := New(ui)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-id=" + token.AccessorID,
"-token=root",
"-policy-name=" + policy.Name,
"-description=test token",
"-format=json",
}
code := cmd.Run(args)
assert.Equal(code, 0)
assert.Empty(ui.ErrorWriter.String())
var jsonOutput json.RawMessage
err := json.Unmarshal([]byte(ui.OutputWriter.String()), &jsonOutput)
require.NoError(t, err, "token unmarshalling error")
})
}