mirror of
https://github.com/status-im/consul.git
synced 2025-01-21 02:59:48 +00:00
a704ebe639
Also update the Docs and fixup the HTTP API to return proper errors when someone attempts to use Namespaces with an OSS agent. Add Namespace HTTP API docs Make all API endpoints disallow unknown fields
300 lines
8.9 KiB
Go
300 lines
8.9 KiB
Go
package tokenupdate
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/consul/api"
|
|
"github.com/hashicorp/consul/command/acl"
|
|
"github.com/hashicorp/consul/command/flags"
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
func New(ui cli.Ui) *cmd {
|
|
c := &cmd{UI: ui}
|
|
c.init()
|
|
return c
|
|
}
|
|
|
|
type cmd struct {
|
|
UI cli.Ui
|
|
flags *flag.FlagSet
|
|
http *flags.HTTPFlags
|
|
help string
|
|
|
|
tokenID string
|
|
policyIDs []string
|
|
policyNames []string
|
|
roleIDs []string
|
|
roleNames []string
|
|
serviceIdents []string
|
|
description string
|
|
mergePolicies bool
|
|
mergeRoles bool
|
|
mergeServiceIdents bool
|
|
showMeta bool
|
|
upgradeLegacy bool
|
|
}
|
|
|
|
func (c *cmd) init() {
|
|
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
|
c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that token metadata such "+
|
|
"as the content hash and raft indices should be shown for each entry")
|
|
c.flags.BoolVar(&c.mergePolicies, "merge-policies", false, "Merge the new policies "+
|
|
"with the existing policies")
|
|
c.flags.BoolVar(&c.mergeRoles, "merge-roles", false, "Merge the new roles "+
|
|
"with the existing roles")
|
|
c.flags.BoolVar(&c.mergeServiceIdents, "merge-service-identities", false, "Merge the new service identities "+
|
|
"with the existing service identities")
|
|
c.flags.StringVar(&c.tokenID, "id", "", "The Accessor ID of the token to update. "+
|
|
"It may be specified as a unique ID prefix but will error if the prefix "+
|
|
"matches multiple token Accessor IDs")
|
|
c.flags.StringVar(&c.description, "description", "", "A description of the token")
|
|
c.flags.Var((*flags.AppendSliceValue)(&c.policyIDs), "policy-id", "ID of a "+
|
|
"policy to use for this token. May be specified multiple times")
|
|
c.flags.Var((*flags.AppendSliceValue)(&c.policyNames), "policy-name", "Name of a "+
|
|
"policy to use for this token. May be specified multiple times")
|
|
c.flags.Var((*flags.AppendSliceValue)(&c.roleIDs), "role-id", "ID of a "+
|
|
"role to use for this token. May be specified multiple times")
|
|
c.flags.Var((*flags.AppendSliceValue)(&c.roleNames), "role-name", "Name of a "+
|
|
"role to use for this token. May be specified multiple times")
|
|
c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+
|
|
"service identity to use for this token. May be specified multiple times. Format is "+
|
|
"the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...")
|
|
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{}
|
|
flags.Merge(c.flags, c.http.ClientFlags())
|
|
flags.Merge(c.flags, c.http.ServerFlags())
|
|
flags.Merge(c.flags, c.http.NamespaceFlags())
|
|
c.help = flags.Usage(help, c.flags)
|
|
}
|
|
|
|
func (c *cmd) Run(args []string) int {
|
|
if err := c.flags.Parse(args); err != nil {
|
|
return 1
|
|
}
|
|
|
|
if c.tokenID == "" {
|
|
c.UI.Error(fmt.Sprintf("Cannot update a token without specifying the -id parameter"))
|
|
return 1
|
|
}
|
|
|
|
client, err := c.http.APIClient()
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
|
return 1
|
|
}
|
|
|
|
tokenID, err := acl.GetTokenIDFromPartial(client, c.tokenID)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error determining token ID: %v", err))
|
|
return 1
|
|
}
|
|
|
|
token, _, err := client.ACL().TokenRead(tokenID, nil)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error when retrieving current token: %v", err))
|
|
return 1
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
parsedServiceIdents, err := acl.ExtractServiceIdentities(c.serviceIdents)
|
|
if err != nil {
|
|
c.UI.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
if c.mergePolicies {
|
|
for _, policyName := range c.policyNames {
|
|
found := false
|
|
for _, link := range token.Policies {
|
|
if link.Name == policyName {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// We could resolve names to IDs here but there isn't any reason why its would be better
|
|
// than allowing the agent to do it.
|
|
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{Name: policyName})
|
|
}
|
|
}
|
|
|
|
for _, policyID := range c.policyIDs {
|
|
policyID, err := acl.GetPolicyIDFromPartial(client, policyID)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err))
|
|
return 1
|
|
}
|
|
found := false
|
|
|
|
for _, link := range token.Policies {
|
|
if link.ID == policyID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{ID: policyID})
|
|
}
|
|
}
|
|
} else {
|
|
token.Policies = nil
|
|
|
|
for _, policyName := range c.policyNames {
|
|
// We could resolve names to IDs here but there isn't any reason why its would be better
|
|
// than allowing the agent to do it.
|
|
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{Name: policyName})
|
|
}
|
|
|
|
for _, policyID := range c.policyIDs {
|
|
policyID, err := acl.GetPolicyIDFromPartial(client, policyID)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err))
|
|
return 1
|
|
}
|
|
token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{ID: policyID})
|
|
}
|
|
}
|
|
|
|
if c.mergeRoles {
|
|
for _, roleName := range c.roleNames {
|
|
found := false
|
|
for _, link := range token.Roles {
|
|
if link.Name == roleName {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// We could resolve names to IDs here but there isn't any reason why its would be better
|
|
// than allowing the agent to do it.
|
|
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{Name: roleName})
|
|
}
|
|
}
|
|
|
|
for _, roleID := range c.roleIDs {
|
|
roleID, err := acl.GetRoleIDFromPartial(client, roleID)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error resolving role ID %s: %v", roleID, err))
|
|
return 1
|
|
}
|
|
found := false
|
|
|
|
for _, link := range token.Roles {
|
|
if link.ID == roleID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{Name: roleID})
|
|
}
|
|
}
|
|
} else {
|
|
token.Roles = nil
|
|
|
|
for _, roleName := range c.roleNames {
|
|
// We could resolve names to IDs here but there isn't any reason why its would be better
|
|
// than allowing the agent to do it.
|
|
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{Name: roleName})
|
|
}
|
|
|
|
for _, roleID := range c.roleIDs {
|
|
roleID, err := acl.GetRoleIDFromPartial(client, roleID)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Error resolving role ID %s: %v", roleID, err))
|
|
return 1
|
|
}
|
|
token.Roles = append(token.Roles, &api.ACLTokenRoleLink{ID: roleID})
|
|
}
|
|
}
|
|
|
|
if c.mergeServiceIdents {
|
|
for _, svcid := range parsedServiceIdents {
|
|
found := -1
|
|
for i, link := range token.ServiceIdentities {
|
|
if link.ServiceName == svcid.ServiceName {
|
|
found = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if found != -1 {
|
|
token.ServiceIdentities[found] = svcid
|
|
} else {
|
|
token.ServiceIdentities = append(token.ServiceIdentities, svcid)
|
|
}
|
|
}
|
|
} else {
|
|
token.ServiceIdentities = parsedServiceIdents
|
|
}
|
|
|
|
token, _, err = client.ACL().TokenUpdate(token, nil)
|
|
if err != nil {
|
|
c.UI.Error(fmt.Sprintf("Failed to update token %s: %v", tokenID, err))
|
|
return 1
|
|
}
|
|
|
|
c.UI.Info("Token updated successfully.")
|
|
acl.PrintToken(token, c.UI, c.showMeta)
|
|
return 0
|
|
}
|
|
|
|
func (c *cmd) Synopsis() string {
|
|
return synopsis
|
|
}
|
|
|
|
func (c *cmd) Help() string {
|
|
return flags.Usage(c.help, nil)
|
|
}
|
|
|
|
const synopsis = "Update an ACL token"
|
|
const help = `
|
|
Usage: consul acl token update [options]
|
|
|
|
This command will update a token. Some parts such as marking the token local
|
|
cannot be changed.
|
|
|
|
Update a token description and take the policies from the existing token:
|
|
|
|
$ consul acl token update -id abcd -description "replication" -merge-policies
|
|
|
|
Update all editable fields of the token:
|
|
|
|
$ consul acl token update -id abcd \
|
|
-description "replication" \
|
|
-policy-name "token-replication" \
|
|
-role-name "db-updater"
|
|
`
|