github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/command/acl/token/update/token_update.go (about)

     1  package tokenupdate
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  
     7  	"github.com/hashicorp/consul/api"
     8  	"github.com/hashicorp/consul/command/acl"
     9  	"github.com/hashicorp/consul/command/flags"
    10  	"github.com/mitchellh/cli"
    11  )
    12  
    13  func New(ui cli.Ui) *cmd {
    14  	c := &cmd{UI: ui}
    15  	c.init()
    16  	return c
    17  }
    18  
    19  type cmd struct {
    20  	UI    cli.Ui
    21  	flags *flag.FlagSet
    22  	http  *flags.HTTPFlags
    23  	help  string
    24  
    25  	tokenID       string
    26  	policyIDs     []string
    27  	policyNames   []string
    28  	description   string
    29  	mergePolicies bool
    30  	showMeta      bool
    31  	upgradeLegacy bool
    32  }
    33  
    34  func (c *cmd) init() {
    35  	c.flags = flag.NewFlagSet("", flag.ContinueOnError)
    36  	c.flags.BoolVar(&c.showMeta, "meta", false, "Indicates that token metadata such "+
    37  		"as the content hash and raft indices should be shown for each entry")
    38  	c.flags.BoolVar(&c.mergePolicies, "merge-policies", false, "Merge the new policies "+
    39  		"with the existing policies")
    40  	c.flags.StringVar(&c.tokenID, "id", "", "The Accessor ID of the token to read. "+
    41  		"It may be specified as a unique ID prefix but will error if the prefix "+
    42  		"matches multiple token Accessor IDs")
    43  	c.flags.StringVar(&c.description, "description", "", "A description of the token")
    44  	c.flags.Var((*flags.AppendSliceValue)(&c.policyIDs), "policy-id", "ID of a "+
    45  		"policy to use for this token. May be specified multiple times")
    46  	c.flags.Var((*flags.AppendSliceValue)(&c.policyNames), "policy-name", "Name of a "+
    47  		"policy to use for this token. May be specified multiple times")
    48  	c.flags.BoolVar(&c.upgradeLegacy, "upgrade-legacy", false, "Add new polices "+
    49  		"to a legacy token replacing all existing rules. This will cause the legacy "+
    50  		"token to behave exactly like a new token but keep the same Secret.\n"+
    51  		"WARNING: you must ensure that the new policy or policies specified grant "+
    52  		"equivalent or appropriate access for the existing clients using this token.")
    53  
    54  	c.http = &flags.HTTPFlags{}
    55  	flags.Merge(c.flags, c.http.ClientFlags())
    56  	flags.Merge(c.flags, c.http.ServerFlags())
    57  	c.help = flags.Usage(help, c.flags)
    58  }
    59  
    60  func (c *cmd) Run(args []string) int {
    61  	if err := c.flags.Parse(args); err != nil {
    62  		return 1
    63  	}
    64  
    65  	if c.tokenID == "" {
    66  		c.UI.Error(fmt.Sprintf("Cannot update a token without specifying the -id parameter"))
    67  		return 1
    68  	}
    69  
    70  	client, err := c.http.APIClient()
    71  	if err != nil {
    72  		c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
    73  		return 1
    74  	}
    75  
    76  	tokenID, err := acl.GetTokenIDFromPartial(client, c.tokenID)
    77  	if err != nil {
    78  		c.UI.Error(fmt.Sprintf("Error determining token ID: %v", err))
    79  		return 1
    80  	}
    81  
    82  	token, _, err := client.ACL().TokenRead(tokenID, nil)
    83  	if err != nil {
    84  		c.UI.Error(fmt.Sprintf("Error when retrieving current token: %v", err))
    85  		return 1
    86  	}
    87  
    88  	if c.upgradeLegacy {
    89  		if token.Rules == "" {
    90  			// This is just for convenience it should actually be harmless to allow it
    91  			// to go through anyway.
    92  			c.UI.Error(fmt.Sprintf("Can't use -upgrade-legacy on a non-legacy token"))
    93  			return 1
    94  		}
    95  		// Reset the rules to nothing forcing this to be updated as a non-legacy
    96  		// token but with same secret.
    97  		token.Rules = ""
    98  	}
    99  
   100  	if c.description != "" {
   101  		// Only update description if the user specified a new one. This does make
   102  		// it impossible to completely clear descriptions from CLI but that seems
   103  		// better than silently deleting descriptions when using command without
   104  		// manually giving the new description. If it's a real issue we can always
   105  		// add another explicit `-remove-description` flag but it feels like an edge
   106  		// case that's not going to be critical to anyone.
   107  		token.Description = c.description
   108  	}
   109  
   110  	if c.mergePolicies {
   111  		for _, policyName := range c.policyNames {
   112  			found := false
   113  			for _, link := range token.Policies {
   114  				if link.Name == policyName {
   115  					found = true
   116  					break
   117  				}
   118  			}
   119  
   120  			if !found {
   121  				// We could resolve names to IDs here but there isn't any reason why its would be better
   122  				// than allowing the agent to do it.
   123  				token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{Name: policyName})
   124  			}
   125  		}
   126  
   127  		for _, policyID := range c.policyIDs {
   128  			policyID, err := acl.GetPolicyIDFromPartial(client, policyID)
   129  			if err != nil {
   130  				c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err))
   131  				return 1
   132  			}
   133  			found := false
   134  
   135  			for _, link := range token.Policies {
   136  				if link.ID == policyID {
   137  					found = true
   138  					break
   139  				}
   140  			}
   141  
   142  			if !found {
   143  				token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{ID: policyID})
   144  			}
   145  		}
   146  	} else {
   147  		token.Policies = nil
   148  
   149  		for _, policyName := range c.policyNames {
   150  			// We could resolve names to IDs here but there isn't any reason why its would be better
   151  			// than allowing the agent to do it.
   152  			token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{Name: policyName})
   153  		}
   154  
   155  		for _, policyID := range c.policyIDs {
   156  			policyID, err := acl.GetPolicyIDFromPartial(client, policyID)
   157  			if err != nil {
   158  				c.UI.Error(fmt.Sprintf("Error resolving policy ID %s: %v", policyID, err))
   159  				return 1
   160  			}
   161  			token.Policies = append(token.Policies, &api.ACLTokenPolicyLink{ID: policyID})
   162  		}
   163  	}
   164  
   165  	token, _, err = client.ACL().TokenUpdate(token, nil)
   166  	if err != nil {
   167  		c.UI.Error(fmt.Sprintf("Failed to update token %s: %v", tokenID, err))
   168  		return 1
   169  	}
   170  
   171  	c.UI.Info("Token updated successfully.")
   172  	acl.PrintToken(token, c.UI, c.showMeta)
   173  	return 0
   174  }
   175  
   176  func (c *cmd) Synopsis() string {
   177  	return synopsis
   178  }
   179  
   180  func (c *cmd) Help() string {
   181  	return flags.Usage(c.help, nil)
   182  }
   183  
   184  const synopsis = "Update an ACL Token"
   185  const help = `
   186  Usage: consul acl token update [options]
   187  
   188      This command will update a token. Some parts such as marking the token local
   189      cannot be changed.
   190  
   191      Update a token description and take the policies from the existing token:
   192  
   193          $ consul acl token update -id abcd -description "replication" -merge-policies
   194  
   195        Update all editable fields of the token:
   196  
   197            $ consul acl token update -id abcd -description "replication" -policy-name "token-replication"
   198  `