2023-03-28 19:12:30 +00:00
// Copyright (c) HashiCorp, Inc.
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-28 19:12:30 +00:00
2018-10-19 16:04:07 +00:00
package tokenupdate
import (
"flag"
"fmt"
2020-02-15 15:06:05 +00:00
"strings"
2018-10-19 16:04:07 +00:00
2022-04-05 21:10:06 +00:00
"github.com/mitchellh/cli"
2018-10-19 16:04:07 +00:00
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/command/acl"
2020-02-15 15:06:05 +00:00
"github.com/hashicorp/consul/command/acl/token"
2018-10-19 16:04:07 +00:00
"github.com/hashicorp/consul/command/flags"
)
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
2023-09-08 12:45:24 +00:00
tokenAccessorID string
policyIDs [ ] string
appendPolicyIDs [ ] string
policyNames [ ] string
appendPolicyNames [ ] string
roleIDs [ ] string
appendRoleIDs [ ] string
roleNames [ ] string
appendRoleNames [ ] string
serviceIdents [ ] string
nodeIdents [ ] string
appendNodeIdents [ ] string
appendServiceIdents [ ] string
appendTemplatedPolicy string
replaceTemplatedPolicy string
appendTemplatedPolicyFile string
replaceTemplatedPolicyFile string
templatedPolicyVariables [ ] string
description string
showMeta bool
format string
2023-02-07 18:26:30 +00:00
2023-03-01 20:00:37 +00:00
// DEPRECATED
2023-03-06 15:00:39 +00:00
mergeServiceIdents bool
mergeNodeIdents bool
mergeRoles bool
mergePolicies bool
tokenID string
2018-10-19 16:04:07 +00:00
}
func ( c * cmd ) init ( ) {
c . flags = flag . NewFlagSet ( "" , flag . ContinueOnError )
2018-10-24 14:24:29 +00:00
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" )
2023-02-07 18:26:30 +00:00
c . flags . StringVar ( & c . tokenAccessorID , "accessor-id" , "" , "The Accessor ID of the token to update. " +
2018-10-19 16:04:07 +00:00
"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 " +
2023-03-01 20:00:37 +00:00
"policy to use for this token. Overwrites existing policies. May be specified multiple times" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . appendPolicyIDs ) , "append-policy-id" , "ID of a " +
"policy to use for this token. The token retains existing policies. May be specified multiple times" )
2018-10-19 16:04:07 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . policyNames ) , "policy-name" , "Name of a " +
2023-03-01 20:00:37 +00:00
"policy to use for this token. Overwrites existing policies. May be specified multiple times" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . appendPolicyNames ) , "append-policy-name" , "Name of a " +
"policy to add to this token. The token retains existing policies. May be specified multiple times" )
2019-04-15 20:43:19 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . roleIDs ) , "role-id" , "ID of a " +
2023-03-01 20:00:37 +00:00
"role to use for this token. Overwrites existing roles. May be specified multiple times" )
2019-04-15 20:43:19 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . roleNames ) , "role-name" , "Name of a " +
2023-03-01 20:00:37 +00:00
"role to use for this token. Overwrites existing roles. May be specified multiple times" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . appendRoleIDs ) , "append-role-id" , "ID of a " +
"role to add to this token. The token retains existing roles. May be specified multiple times" )
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . appendRoleNames ) , "append-role-name" , "Name of a " +
"role to add to this token. The token retains existing roles. May be specified multiple times" )
2019-04-08 18:19:09 +00:00
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,..." )
2023-03-06 15:00:39 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . appendServiceIdents ) , "append-service-identity" , "Name of a " +
"service identity to use for this token. This token retains existing service identities. May be specified" +
"multiple times. Format is the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,..." )
2020-06-16 16:54:27 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . nodeIdents ) , "node-identity" , "Name of a " +
"node identity to use for this token. May be specified multiple times. Format is " +
"NODENAME:DATACENTER" )
2023-03-06 15:00:39 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . appendNodeIdents ) , "append-node-identity" , "Name of a " +
"node identity to use for this token. This token retains existing node identities. May be " +
"specified multiple times. Format is NODENAME:DATACENTER" )
2023-09-08 12:45:24 +00:00
c . flags . Var ( ( * flags . AppendSliceValue ) ( & c . templatedPolicyVariables ) , "var" , "Templated policy variables." +
" Must be used in combination with -replace-templated-policy or -append-templated-policy flags to specify required variables." +
" May be specified multiple times with different variables." +
" Format is VariableName:Value" )
c . flags . StringVar ( & c . appendTemplatedPolicy , "append-templated-policy" , "" , "The templated policy name to attach to the token's existing templated policies." +
"Use -var flag to specify variables when required. Token retains existing templated policies." )
c . flags . StringVar ( & c . replaceTemplatedPolicy , "replace-templated-policy" , "" , "The templated policy name to use replace token's existing templated policies." +
" Use -var flag to specify variables when required. Overwrites token's existing templated policies." )
c . flags . StringVar ( & c . appendTemplatedPolicyFile , "append-templated-policy-file" , "" , "Path to a file containing templated policy names and variables." +
" Works similarly to `-append-templated-policy`. The token retains existing templated policies." )
c . flags . StringVar ( & c . replaceTemplatedPolicyFile , "replace-templated-policy-file" , "" , "Path to a file containing templated policy names and variables." +
" Works similarly to `-replace-templated-policy`. Overwrites the token's existing templated policies." )
2020-02-15 15:06:05 +00:00
c . flags . StringVar (
& c . format ,
"format" ,
token . PrettyFormat ,
fmt . Sprintf ( "Output format {%s}" , strings . Join ( token . GetSupportedFormats ( ) , "|" ) ) ,
)
2018-11-05 14:32:09 +00:00
2018-10-19 16:04:07 +00:00
c . http = & flags . HTTPFlags { }
flags . Merge ( c . flags , c . http . ClientFlags ( ) )
flags . Merge ( c . flags , c . http . ServerFlags ( ) )
2021-07-21 19:45:24 +00:00
flags . Merge ( c . flags , c . http . MultiTenancyFlags ( ) )
2018-10-19 16:04:07 +00:00
c . help = flags . Usage ( help , c . flags )
2023-02-07 18:26:30 +00:00
// Deprecations
2023-03-01 20:00:37 +00:00
c . flags . StringVar ( & c . tokenID , "id" , "" , "DEPRECATED. Use -accessor-id instead." )
c . flags . BoolVar ( & c . mergePolicies , "merge-policies" , false , "DEPRECATED. " +
"Use -append-policy-id or -append-policy-name instead." )
c . flags . BoolVar ( & c . mergeRoles , "merge-roles" , false , "DEPRECATED. " +
"Use -append-role-id or -append-role-name instead." )
2023-03-06 15:00:39 +00:00
c . flags . BoolVar ( & c . mergeServiceIdents , "merge-service-identities" , false , "DEPRECATED. " +
"Use -append-service-identity instead." )
c . flags . BoolVar ( & c . mergeNodeIdents , "merge-node-identities" , false , "DEPRECATED. " +
"Use -append-node-identity instead." )
2018-10-19 16:04:07 +00:00
}
func ( c * cmd ) Run ( args [ ] string ) int {
if err := c . flags . Parse ( args ) ; err != nil {
return 1
}
2023-02-07 18:26:30 +00:00
tokenAccessor := c . tokenAccessorID
if tokenAccessor == "" {
if c . tokenID == "" {
c . UI . Error ( "Cannot update a token without specifying the -accessor-id parameter" )
return 1
} else {
tokenAccessor = c . tokenID
c . UI . Warn ( "Use the -accessor-id parameter to specify token by Accessor ID" )
}
2018-10-19 16:04:07 +00:00
}
client , err := c . http . APIClient ( )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error connecting to Consul agent: %s" , err ) )
return 1
}
2023-02-07 18:26:30 +00:00
tok , err := acl . GetTokenAccessorIDFromPartial ( client , tokenAccessor )
2018-10-19 16:04:07 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error determining token ID: %v" , err ) )
return 1
}
2023-02-07 18:26:30 +00:00
t , _ , err := client . ACL ( ) . TokenRead ( tok , nil )
2018-10-19 16:04:07 +00:00
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error when retrieving current token: %v" , err ) )
return 1
}
2018-11-05 14:32:09 +00:00
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.
2020-02-15 15:06:05 +00:00
t . Description = c . description
2018-11-05 14:32:09 +00:00
}
2018-10-19 16:04:07 +00:00
2023-03-06 15:00:39 +00:00
hasAppendServiceFields := len ( c . appendServiceIdents ) > 0
hasServiceFields := len ( c . serviceIdents ) > 0
if hasAppendServiceFields && hasServiceFields {
c . UI . Error ( "Cannot combine the use of service-identity flag with append-service-identity. " +
"To set or overwrite existing service identities, use -service-identity. " +
"To append to existing service identities, use -append-service-identity." )
return 1
}
2019-04-08 18:19:09 +00:00
parsedServiceIdents , err := acl . ExtractServiceIdentities ( c . serviceIdents )
2023-03-06 15:00:39 +00:00
if hasAppendServiceFields {
parsedServiceIdents , err = acl . ExtractServiceIdentities ( c . appendServiceIdents )
}
2019-04-08 18:19:09 +00:00
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
2023-03-06 15:00:39 +00:00
hasAppendNodeFields := len ( c . appendNodeIdents ) > 0
hasNodeFields := len ( c . nodeIdents ) > 0
if hasAppendNodeFields && hasNodeFields {
c . UI . Error ( "Cannot combine the use of node-identity flag with append-node-identity. " +
"To set or overwrite existing node identities, use -node-identity. " +
"To append to existing node identities, use -append-node-identity." )
return 1
}
2020-06-16 16:54:27 +00:00
parsedNodeIdents , err := acl . ExtractNodeIdentities ( c . nodeIdents )
2023-03-06 15:00:39 +00:00
if hasAppendNodeFields {
parsedNodeIdents , err = acl . ExtractNodeIdentities ( c . appendNodeIdents )
}
2020-06-16 16:54:27 +00:00
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
2023-09-08 12:45:24 +00:00
hasAppendTemplatedPolicies := len ( c . appendTemplatedPolicy ) > 0 || len ( c . appendTemplatedPolicyFile ) > 0
hasReplaceTemplatedPolicies := len ( c . replaceTemplatedPolicy ) > 0 || len ( c . replaceTemplatedPolicyFile ) > 0
if hasReplaceTemplatedPolicies && hasAppendTemplatedPolicies {
c . UI . Error ( "Cannot combine the use of -append-templated-policy flags with replace-templated-policy. " +
"To set or overwrite existing templated policies, use -replace-templated-policy or -replace-templated-policy-file. " +
"To append to existing templated policies, use -append-templated-policy or -append-templated-policy-file." )
return 1
}
parsedTemplatedPolicies , err := acl . ExtractTemplatedPolicies ( c . replaceTemplatedPolicy , c . replaceTemplatedPolicyFile , c . templatedPolicyVariables )
if hasAppendTemplatedPolicies {
parsedTemplatedPolicies , err = acl . ExtractTemplatedPolicies ( c . appendTemplatedPolicy , c . appendTemplatedPolicyFile , c . templatedPolicyVariables )
}
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
2018-10-19 16:04:07 +00:00
if c . mergePolicies {
2023-03-01 20:00:37 +00:00
c . UI . Warn ( "merge-policies is deprecated and will be removed in a future Consul version. " +
"Use `append-policy-name` or `append-policy-id` instead." )
2018-10-19 16:04:07 +00:00
for _ , policyName := range c . policyNames {
found := false
2020-02-15 15:06:05 +00:00
for _ , link := range t . Policies {
2018-10-19 16:04:07 +00:00
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.
2020-02-15 15:06:05 +00:00
t . Policies = append ( t . Policies , & api . ACLTokenPolicyLink { Name : policyName } )
2018-10-19 16:04:07 +00:00
}
}
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
2020-02-15 15:06:05 +00:00
for _ , link := range t . Policies {
2018-10-19 16:04:07 +00:00
if link . ID == policyID {
found = true
break
}
}
if ! found {
2020-02-15 15:06:05 +00:00
t . Policies = append ( t . Policies , & api . ACLTokenPolicyLink { ID : policyID } )
2018-10-19 16:04:07 +00:00
}
}
} else {
2023-03-01 20:00:37 +00:00
hasAddPolicyFields := len ( c . appendPolicyNames ) > 0 || len ( c . appendPolicyIDs ) > 0
hasPolicyFields := len ( c . policyIDs ) > 0 || len ( c . policyNames ) > 0
if hasPolicyFields && hasAddPolicyFields {
c . UI . Error ( "Cannot combine the use of policy-id/policy-name flags with append- variants. " +
"To set or overwrite existing policies, use -policy-id or -policy-name. " +
"To append to existing policies, use -append-policy-id or -append-policy-name." )
return 1
}
policyIDs := c . appendPolicyIDs
policyNames := c . appendPolicyNames
if hasPolicyFields {
policyIDs = c . policyIDs
policyNames = c . policyNames
t . Policies = nil
}
for _ , policyName := range policyNames {
2018-10-19 16:04:07 +00:00
// 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.
2020-02-15 15:06:05 +00:00
t . Policies = append ( t . Policies , & api . ACLTokenPolicyLink { Name : policyName } )
2018-10-19 16:04:07 +00:00
}
2023-03-01 20:00:37 +00:00
for _ , policyID := range policyIDs {
2018-10-19 16:04:07 +00:00
policyID , err := acl . GetPolicyIDFromPartial ( client , policyID )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error resolving policy ID %s: %v" , policyID , err ) )
return 1
}
2020-02-15 15:06:05 +00:00
t . Policies = append ( t . Policies , & api . ACLTokenPolicyLink { ID : policyID } )
2018-10-19 16:04:07 +00:00
}
}
2019-04-15 20:43:19 +00:00
if c . mergeRoles {
2023-03-01 20:00:37 +00:00
c . UI . Warn ( "merge-roles is deprecated and will be removed in a future Consul version. " +
"Use `append-role-name` or `append-role-id` instead." )
2019-04-15 20:43:19 +00:00
for _ , roleName := range c . roleNames {
found := false
2020-02-15 15:06:05 +00:00
for _ , link := range t . Roles {
2019-04-15 20:43:19 +00:00
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.
2020-02-15 15:06:05 +00:00
t . Roles = append ( t . Roles , & api . ACLTokenRoleLink { Name : roleName } )
2019-04-15 20:43:19 +00:00
}
}
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
2020-02-15 15:06:05 +00:00
for _ , link := range t . Roles {
2019-04-15 20:43:19 +00:00
if link . ID == roleID {
found = true
break
}
}
if ! found {
2020-02-15 15:06:05 +00:00
t . Roles = append ( t . Roles , & api . ACLTokenRoleLink { Name : roleID } )
2019-04-15 20:43:19 +00:00
}
}
} else {
2023-03-01 20:00:37 +00:00
hasAddRoleFields := len ( c . appendRoleNames ) > 0 || len ( c . appendRoleIDs ) > 0
hasRoleFields := len ( c . roleIDs ) > 0 || len ( c . roleNames ) > 0
2019-04-15 20:43:19 +00:00
2023-03-01 20:00:37 +00:00
if hasRoleFields && hasAddRoleFields {
c . UI . Error ( "Cannot combine the use of role-id/role-name flags with append- variants. " +
"To set or overwrite existing roles, use -role-id or -role-name. " +
"To append to existing roles, use -append-role-id or -append-role-name." )
return 1
}
roleNames := c . appendRoleNames
roleIDs := c . appendRoleIDs
if hasRoleFields {
roleNames = c . roleNames
roleIDs = c . roleIDs
t . Roles = nil
}
for _ , roleName := range roleNames {
2019-04-15 20:43:19 +00:00
// 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.
2020-02-15 15:06:05 +00:00
t . Roles = append ( t . Roles , & api . ACLTokenRoleLink { Name : roleName } )
2019-04-15 20:43:19 +00:00
}
2023-03-01 20:00:37 +00:00
for _ , roleID := range roleIDs {
2019-04-15 20:43:19 +00:00
roleID , err := acl . GetRoleIDFromPartial ( client , roleID )
if err != nil {
c . UI . Error ( fmt . Sprintf ( "Error resolving role ID %s: %v" , roleID , err ) )
return 1
}
2020-02-15 15:06:05 +00:00
t . Roles = append ( t . Roles , & api . ACLTokenRoleLink { ID : roleID } )
2019-04-15 20:43:19 +00:00
}
}
2023-03-06 15:00:39 +00:00
if c . mergeServiceIdents || hasAppendServiceFields {
2019-04-08 18:19:09 +00:00
for _ , svcid := range parsedServiceIdents {
found := - 1
2020-02-15 15:06:05 +00:00
for i , link := range t . ServiceIdentities {
2019-04-08 18:19:09 +00:00
if link . ServiceName == svcid . ServiceName {
found = i
break
}
}
if found != - 1 {
2020-02-15 15:06:05 +00:00
t . ServiceIdentities [ found ] = svcid
2019-04-08 18:19:09 +00:00
} else {
2020-02-15 15:06:05 +00:00
t . ServiceIdentities = append ( t . ServiceIdentities , svcid )
2019-04-08 18:19:09 +00:00
}
}
} else {
2020-02-15 15:06:05 +00:00
t . ServiceIdentities = parsedServiceIdents
2019-04-08 18:19:09 +00:00
}
2023-03-06 15:00:39 +00:00
if c . mergeNodeIdents || hasAppendNodeFields {
2020-06-16 16:54:27 +00:00
for _ , nodeid := range parsedNodeIdents {
found := false
for _ , link := range t . NodeIdentities {
if link . NodeName == nodeid . NodeName && link . Datacenter == nodeid . Datacenter {
found = true
break
}
}
if ! found {
t . NodeIdentities = append ( t . NodeIdentities , nodeid )
}
}
} else {
t . NodeIdentities = parsedNodeIdents
}
2023-09-08 12:45:24 +00:00
if hasReplaceTemplatedPolicies {
t . TemplatedPolicies = parsedTemplatedPolicies
} else {
t . TemplatedPolicies = append ( t . TemplatedPolicies , parsedTemplatedPolicies ... )
}
2020-02-15 15:06:05 +00:00
t , _ , err = client . ACL ( ) . TokenUpdate ( t , nil )
2018-10-19 16:04:07 +00:00
if err != nil {
2023-02-07 18:26:30 +00:00
c . UI . Error ( fmt . Sprintf ( "Failed to update token %s: %v" , tok , err ) )
2018-10-19 16:04:07 +00:00
return 1
}
2020-02-15 15:06:05 +00:00
formatter , err := token . NewFormatter ( c . format , c . showMeta )
if err != nil {
c . UI . Error ( err . Error ( ) )
return 1
}
out , err := formatter . FormatToken ( t )
if err != nil {
c . UI . Error ( err . Error ( ) )
2020-03-26 15:24:21 +00:00
return 1
2020-02-15 15:06:05 +00:00
}
if out != "" {
c . UI . Info ( out )
}
2018-10-19 16:04:07 +00:00
return 0
}
func ( c * cmd ) Synopsis ( ) string {
return synopsis
}
func ( c * cmd ) Help ( ) string {
return flags . Usage ( c . help , nil )
}
2021-07-21 19:45:24 +00:00
const (
synopsis = "Update an ACL token"
help = `
2018-10-19 16:04:07 +00:00
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 :
2023-02-07 18:26:30 +00:00
$ consul acl token update - accessor - id abcd - description "replication" - merge - policies
2018-10-19 16:04:07 +00:00
2019-04-08 17:05:51 +00:00
Update all editable fields of the token :
2018-10-19 16:04:07 +00:00
2023-02-07 18:26:30 +00:00
$ consul acl token update - accessor - id abcd \
2019-04-08 17:05:51 +00:00
- description "replication" \
2019-04-15 20:43:19 +00:00
- policy - name "token-replication" \
2023-09-08 12:45:24 +00:00
- role - name "db-updater" \
- templated - policy "builtin/service" \
- var "name:web"
2018-10-19 16:04:07 +00:00
`
2021-07-21 19:45:24 +00:00
)