mirror of
https://github.com/status-im/consul.git
synced 2025-01-20 18:50:04 +00:00
Allow ACL legacy migration via CLI (#4882)
* Adds a flag to `consul acl token update` that allows legacy ACLs to be upgraded via the CLI. Also fixes a bug where descriptions are deleted if not specified. * Remove debug
This commit is contained in:
parent
57dd160f40
commit
37d88cad29
@ -135,5 +135,5 @@ Usage: consul acl translate-rules [options] TRANSLATE
|
|||||||
|
|
||||||
Translate rules for a legacy ACL token using its AccessorID:
|
Translate rules for a legacy ACL token using its AccessorID:
|
||||||
|
|
||||||
$ consul acl translate-rules 429cd746-03d5-4bbb-a83a-18b164171c89
|
$ consul acl translate-rules -token-accessor 429cd746-03d5-4bbb-a83a-18b164171c89
|
||||||
`
|
`
|
||||||
|
@ -28,6 +28,7 @@ type cmd struct {
|
|||||||
description string
|
description string
|
||||||
mergePolicies bool
|
mergePolicies bool
|
||||||
showMeta bool
|
showMeta bool
|
||||||
|
upgradeLegacy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmd) init() {
|
func (c *cmd) init() {
|
||||||
@ -44,6 +45,12 @@ func (c *cmd) init() {
|
|||||||
"policy to use for this token. May be specified multiple times")
|
"policy to use for this token. May be specified multiple times")
|
||||||
c.flags.Var((*flags.AppendSliceValue)(&c.policyNames), "policy-name", "Name of a "+
|
c.flags.Var((*flags.AppendSliceValue)(&c.policyNames), "policy-name", "Name of a "+
|
||||||
"policy to use for this token. May be specified multiple times")
|
"policy to use for this token. May be specified multiple times")
|
||||||
|
c.flags.BoolVar(&c.upgradeLegacy, "upgrade-legacy", false, "Add new polices "+
|
||||||
|
"to a legacy token replacing all existing rules. This will cause the legacy "+
|
||||||
|
"token to behave exactly like a new token but keep the same Secret.\n"+
|
||||||
|
"WARNING: you must ensure that the new policy or policies specified grant "+
|
||||||
|
"equivalent or appropriate access for the existing clients using this token.")
|
||||||
|
|
||||||
c.http = &flags.HTTPFlags{}
|
c.http = &flags.HTTPFlags{}
|
||||||
flags.Merge(c.flags, c.http.ClientFlags())
|
flags.Merge(c.flags, c.http.ClientFlags())
|
||||||
flags.Merge(c.flags, c.http.ServerFlags())
|
flags.Merge(c.flags, c.http.ServerFlags())
|
||||||
@ -78,7 +85,27 @@ func (c *cmd) Run(args []string) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
token.Description = c.description
|
if c.upgradeLegacy {
|
||||||
|
if token.Rules == "" {
|
||||||
|
// This is just for convenience it should actually be harmless to allow it
|
||||||
|
// to go through anyway.
|
||||||
|
c.UI.Error(fmt.Sprintf("Can't use -upgrade-legacy on a non-legacy token"))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
// Reset the rules to nothing forcing this to be updated as a non-legacy
|
||||||
|
// token but with same secret.
|
||||||
|
token.Rules = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.description != "" {
|
||||||
|
// Only update description if the user specified a new one. This does make
|
||||||
|
// it impossible to completely clear descriptions from CLI but that seems
|
||||||
|
// better than silently deleting descriptions when using command without
|
||||||
|
// manually giving the new description. If it's a real issue we can always
|
||||||
|
// add another explicit `-remove-description` flag but it feels like an edge
|
||||||
|
// case that's not going to be critical to anyone.
|
||||||
|
token.Description = c.description
|
||||||
|
}
|
||||||
|
|
||||||
if c.mergePolicies {
|
if c.mergePolicies {
|
||||||
for _, policyName := range c.policyNames {
|
for _, policyName := range c.policyNames {
|
||||||
|
@ -5,11 +5,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent"
|
"github.com/hashicorp/consul/agent"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/logger"
|
"github.com/hashicorp/consul/logger"
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
"github.com/hashicorp/consul/testutil"
|
"github.com/hashicorp/consul/testutil"
|
||||||
|
"github.com/hashicorp/consul/testutil/retry"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@ -25,6 +28,8 @@ func TestTokenUpdateCommand_noTabs(t *testing.T) {
|
|||||||
func TestTokenUpdateCommand(t *testing.T) {
|
func TestTokenUpdateCommand(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
// Alias because we need to access require package in Retry below
|
||||||
|
req := require.New(t)
|
||||||
|
|
||||||
testDir := testutil.TempDir(t, "acl")
|
testDir := testutil.TempDir(t, "acl")
|
||||||
defer os.RemoveAll(testDir)
|
defer os.RemoveAll(testDir)
|
||||||
@ -44,7 +49,6 @@ func TestTokenUpdateCommand(t *testing.T) {
|
|||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
||||||
ui := cli.NewMockUi()
|
ui := cli.NewMockUi()
|
||||||
cmd := New(ui)
|
|
||||||
|
|
||||||
// Create a policy
|
// Create a policy
|
||||||
client := a.Client()
|
client := a.Client()
|
||||||
@ -53,17 +57,31 @@ func TestTokenUpdateCommand(t *testing.T) {
|
|||||||
&api.ACLPolicy{Name: "test-policy"},
|
&api.ACLPolicy{Name: "test-policy"},
|
||||||
&api.WriteOptions{Token: "root"},
|
&api.WriteOptions{Token: "root"},
|
||||||
)
|
)
|
||||||
assert.NoError(err)
|
req.NoError(err)
|
||||||
|
|
||||||
// create a token
|
// create a token
|
||||||
token, _, err := client.ACL().TokenCreate(
|
token, _, err := client.ACL().TokenCreate(
|
||||||
&api.ACLToken{Description: "test"},
|
&api.ACLToken{Description: "test"},
|
||||||
&api.WriteOptions{Token: "root"},
|
&api.WriteOptions{Token: "root"},
|
||||||
)
|
)
|
||||||
assert.NoError(err)
|
req.NoError(err)
|
||||||
|
|
||||||
|
// 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"},
|
||||||
|
)
|
||||||
|
req.NoError(err)
|
||||||
|
|
||||||
|
// We fetch the legacy token later to give server time to async background
|
||||||
|
// upgrade it.
|
||||||
|
|
||||||
// update with policy by name
|
// update with policy by name
|
||||||
{
|
{
|
||||||
|
cmd := New(ui)
|
||||||
args := []string{
|
args := []string{
|
||||||
"-http-addr=" + a.HTTPAddr(),
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
"-id=" + token.AccessorID,
|
"-id=" + token.AccessorID,
|
||||||
@ -86,6 +104,7 @@ func TestTokenUpdateCommand(t *testing.T) {
|
|||||||
|
|
||||||
// update with policy by id
|
// update with policy by id
|
||||||
{
|
{
|
||||||
|
cmd := New(ui)
|
||||||
args := []string{
|
args := []string{
|
||||||
"-http-addr=" + a.HTTPAddr(),
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
"-id=" + token.AccessorID,
|
"-id=" + token.AccessorID,
|
||||||
@ -105,4 +124,68 @@ func TestTokenUpdateCommand(t *testing.T) {
|
|||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.NotNil(token)
|
assert.NotNil(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update with no description shouldn't delete the current description
|
||||||
|
{
|
||||||
|
cmd := New(ui)
|
||||||
|
args := []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-id=" + token.AccessorID,
|
||||||
|
"-token=root",
|
||||||
|
"-policy-name=" + policy.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cmd.Run(args)
|
||||||
|
assert.Equal(code, 0)
|
||||||
|
assert.Empty(ui.ErrorWriter.String())
|
||||||
|
|
||||||
|
token, _, err := client.ACL().TokenRead(
|
||||||
|
token.AccessorID,
|
||||||
|
&api.QueryOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.NotNil(token)
|
||||||
|
assert.Equal("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})
|
||||||
|
r.Check(err)
|
||||||
|
require.NotEmpty(r, legacyToken.AccessorID)
|
||||||
|
})
|
||||||
|
|
||||||
|
// upgrade legacy token should replace rules and leave token in a "new" state!
|
||||||
|
{
|
||||||
|
cmd := New(ui)
|
||||||
|
args := []string{
|
||||||
|
"-http-addr=" + a.HTTPAddr(),
|
||||||
|
"-id=" + legacyToken.AccessorID,
|
||||||
|
"-token=root",
|
||||||
|
"-policy-name=" + policy.Name,
|
||||||
|
"-upgrade-legacy",
|
||||||
|
}
|
||||||
|
|
||||||
|
code := cmd.Run(args)
|
||||||
|
assert.Equal(code, 0)
|
||||||
|
assert.Empty(ui.ErrorWriter.String())
|
||||||
|
|
||||||
|
gotToken, _, err := client.ACL().TokenRead(
|
||||||
|
legacyToken.AccessorID,
|
||||||
|
&api.QueryOptions{Token: "root"},
|
||||||
|
)
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.NotNil(gotToken)
|
||||||
|
// Description shouldn't change
|
||||||
|
assert.Equal("Legacy token", gotToken.Description)
|
||||||
|
assert.Len(gotToken.Policies, 1)
|
||||||
|
// Rules should now be empty meaning this is no longer a legacy token
|
||||||
|
assert.Empty(gotToken.Rules)
|
||||||
|
// Secret should not have changes
|
||||||
|
assert.Equal(legacyToken.SecretID, gotToken.SecretID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user