1package authmethodupdate 2 3import ( 4 "encoding/json" 5 "flag" 6 "fmt" 7 "io" 8 "strings" 9 "time" 10 11 "github.com/hashicorp/consul/api" 12 "github.com/hashicorp/consul/command/acl/authmethod" 13 "github.com/hashicorp/consul/command/flags" 14 "github.com/hashicorp/consul/command/helpers" 15 "github.com/mitchellh/cli" 16) 17 18func New(ui cli.Ui) *cmd { 19 c := &cmd{UI: ui} 20 c.init() 21 return c 22} 23 24type cmd struct { 25 UI cli.Ui 26 flags *flag.FlagSet 27 http *flags.HTTPFlags 28 help string 29 30 name string 31 32 displayName string 33 description string 34 maxTokenTTL time.Duration 35 tokenLocality string 36 config string 37 38 k8sHost string 39 k8sCACert string 40 k8sServiceAccountJWT string 41 42 noMerge bool 43 showMeta bool 44 format string 45 46 testStdin io.Reader 47 48 enterpriseCmd 49} 50 51func (c *cmd) init() { 52 c.flags = flag.NewFlagSet("", flag.ContinueOnError) 53 54 c.flags.BoolVar( 55 &c.showMeta, 56 "meta", 57 false, 58 "Indicates that auth method metadata such "+ 59 "as the raft indices should be shown for each entry.", 60 ) 61 62 c.flags.StringVar( 63 &c.name, 64 "name", 65 "", 66 "The auth method name.", 67 ) 68 69 c.flags.StringVar( 70 &c.displayName, 71 "display-name", 72 "", 73 "An optional name to use instead of the name when displaying this auth method in a UI.", 74 ) 75 76 c.flags.StringVar( 77 &c.description, 78 "description", 79 "", 80 "A description of the auth method.", 81 ) 82 83 c.flags.DurationVar( 84 &c.maxTokenTTL, 85 "max-token-ttl", 86 0, 87 "Duration of time all tokens created by this auth method should be valid for", 88 ) 89 c.flags.StringVar( 90 &c.tokenLocality, 91 "token-locality", 92 "", 93 "Defines the kind of token that this auth method should produce. "+ 94 "This can be either 'local' or 'global'. If empty the value of 'local' is assumed.", 95 ) 96 97 c.flags.StringVar( 98 &c.config, 99 "config", 100 "", 101 "The configuration for the auth method. Must be JSON. The config is updated as one field"+ 102 "May be prefixed with '@' to indicate that the value is a file path to load the config from. "+ 103 "'-' may also be given to indicate that the config are available on stdin. ", 104 ) 105 106 c.flags.StringVar( 107 &c.k8sHost, 108 "kubernetes-host", 109 "", 110 "Address of the Kubernetes API server. This flag is required for type=kubernetes.", 111 ) 112 c.flags.StringVar( 113 &c.k8sCACert, 114 "kubernetes-ca-cert", 115 "", 116 "PEM encoded CA cert for use by the TLS client used to talk with the "+ 117 "Kubernetes API. May be prefixed with '@' to indicate that the "+ 118 "value is a file path to load the cert from. "+ 119 "This flag is required for type=kubernetes.", 120 ) 121 c.flags.StringVar( 122 &c.k8sServiceAccountJWT, 123 "kubernetes-service-account-jwt", 124 "", 125 "A Kubernetes service account JWT used to access the TokenReview API to "+ 126 "validate other JWTs during login. "+ 127 "This flag is required for type=kubernetes.", 128 ) 129 130 c.flags.BoolVar(&c.noMerge, "no-merge", false, "Do not merge the current auth method "+ 131 "information with what is provided to the command. Instead overwrite all fields "+ 132 "with the exception of the name which is immutable.") 133 134 c.flags.StringVar( 135 &c.format, 136 "format", 137 authmethod.PrettyFormat, 138 fmt.Sprintf("Output format {%s}", strings.Join(authmethod.GetSupportedFormats(), "|")), 139 ) 140 141 c.initEnterpriseFlags() 142 143 c.http = &flags.HTTPFlags{} 144 flags.Merge(c.flags, c.http.ClientFlags()) 145 flags.Merge(c.flags, c.http.ServerFlags()) 146 flags.Merge(c.flags, c.http.NamespaceFlags()) 147 c.help = flags.Usage(help, c.flags) 148} 149 150func (c *cmd) Run(args []string) int { 151 if err := c.flags.Parse(args); err != nil { 152 return 1 153 } 154 155 if c.name == "" { 156 c.UI.Error(fmt.Sprintf("Cannot update an auth method without specifying the -name parameter")) 157 return 1 158 } 159 160 client, err := c.http.APIClient() 161 if err != nil { 162 c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err)) 163 return 1 164 } 165 166 // Regardless of merge, we need to fetch the prior immutable fields first. 167 currentAuthMethod, _, err := client.ACL().AuthMethodRead(c.name, nil) 168 if err != nil { 169 c.UI.Error(fmt.Sprintf("Error when retrieving current auth method: %v", err)) 170 return 1 171 } else if currentAuthMethod == nil { 172 c.UI.Error(fmt.Sprintf("Auth method not found with name %q", c.name)) 173 return 1 174 } 175 176 if c.k8sCACert != "" { 177 c.k8sCACert, err = helpers.LoadDataSource(c.k8sCACert, c.testStdin) 178 if err != nil { 179 c.UI.Error(fmt.Sprintf("Invalid '-kubernetes-ca-cert' value: %v", err)) 180 return 1 181 } else if c.k8sCACert == "" { 182 c.UI.Error(fmt.Sprintf("Kubernetes CA Cert is empty")) 183 return 1 184 } 185 } 186 187 var method *api.ACLAuthMethod 188 if c.noMerge { 189 method = &api.ACLAuthMethod{ 190 Name: currentAuthMethod.Name, 191 Type: currentAuthMethod.Type, 192 DisplayName: c.displayName, 193 Description: c.description, 194 TokenLocality: c.tokenLocality, 195 } 196 if c.maxTokenTTL > 0 { 197 method.MaxTokenTTL = c.maxTokenTTL 198 } 199 200 if err := c.enterprisePopulateAuthMethod(method); err != nil { 201 c.UI.Error(err.Error()) 202 return 1 203 } 204 205 if c.config != "" { 206 if c.k8sHost != "" || c.k8sCACert != "" || c.k8sServiceAccountJWT != "" { 207 c.UI.Error(fmt.Sprintf("Cannot use command line arguments with '-config' flag")) 208 return 1 209 } 210 data, err := helpers.LoadDataSource(c.config, c.testStdin) 211 if err != nil { 212 c.UI.Error(fmt.Sprintf("Error loading configuration file: %v", err)) 213 return 1 214 } 215 if err := json.Unmarshal([]byte(data), &method.Config); err != nil { 216 c.UI.Error(fmt.Sprintf("Error parsing JSON for auth method config: %v", err)) 217 return 1 218 } 219 } 220 221 if currentAuthMethod.Type == "kubernetes" { 222 if c.k8sHost == "" { 223 c.UI.Error(fmt.Sprintf("Missing required '-kubernetes-host' flag")) 224 return 1 225 } else if c.k8sCACert == "" { 226 c.UI.Error(fmt.Sprintf("Missing required '-kubernetes-ca-cert' flag")) 227 return 1 228 } else if c.k8sServiceAccountJWT == "" { 229 c.UI.Error(fmt.Sprintf("Missing required '-kubernetes-service-account-jwt' flag")) 230 return 1 231 } 232 233 method.Config = map[string]interface{}{ 234 "Host": c.k8sHost, 235 "CACert": c.k8sCACert, 236 "ServiceAccountJWT": c.k8sServiceAccountJWT, 237 } 238 } 239 } else { 240 methodCopy := *currentAuthMethod 241 method = &methodCopy 242 if c.description != "" { 243 method.Description = c.description 244 } 245 if c.displayName != "" { 246 method.DisplayName = c.displayName 247 } 248 if c.maxTokenTTL > 0 { 249 method.MaxTokenTTL = c.maxTokenTTL 250 } 251 if c.tokenLocality != "" { 252 method.TokenLocality = c.tokenLocality 253 } 254 if err := c.enterprisePopulateAuthMethod(method); err != nil { 255 c.UI.Error(err.Error()) 256 return 1 257 } 258 if c.config != "" { 259 if c.k8sHost != "" || c.k8sCACert != "" || c.k8sServiceAccountJWT != "" { 260 c.UI.Error(fmt.Sprintf("Cannot use command line arguments with '-config' flag")) 261 return 1 262 } 263 data, err := helpers.LoadDataSource(c.config, c.testStdin) 264 if err != nil { 265 c.UI.Error(fmt.Sprintf("Error loading configuration file: %v", err)) 266 return 1 267 } 268 // Don't attempt a deep merge. 269 method.Config = make(map[string]interface{}) 270 if err := json.Unmarshal([]byte(data), &method.Config); err != nil { 271 c.UI.Error(fmt.Sprintf("Error parsing JSON for auth method config: %v", err)) 272 return 1 273 } 274 } 275 if method.Config == nil { 276 method.Config = make(map[string]interface{}) 277 } 278 if currentAuthMethod.Type == "kubernetes" { 279 if c.k8sHost != "" { 280 method.Config["Host"] = c.k8sHost 281 } 282 if c.k8sCACert != "" { 283 method.Config["CACert"] = c.k8sCACert 284 } 285 if c.k8sServiceAccountJWT != "" { 286 method.Config["ServiceAccountJWT"] = c.k8sServiceAccountJWT 287 } 288 } 289 } 290 291 method, _, err = client.ACL().AuthMethodUpdate(method, nil) 292 if err != nil { 293 c.UI.Error(fmt.Sprintf("Error updating auth method %q: %v", c.name, err)) 294 return 1 295 } 296 297 formatter, err := authmethod.NewFormatter(c.format, c.showMeta) 298 if err != nil { 299 c.UI.Error(err.Error()) 300 return 1 301 } 302 303 out, err := formatter.FormatAuthMethod(method) 304 if err != nil { 305 c.UI.Error(err.Error()) 306 return 1 307 } 308 if out != "" { 309 c.UI.Info(out) 310 } 311 312 return 0 313} 314 315func (c *cmd) Synopsis() string { 316 return synopsis 317} 318 319func (c *cmd) Help() string { 320 return flags.Usage(c.help, nil) 321} 322 323const synopsis = "Update an ACL auth method" 324const help = ` 325Usage: consul acl auth-method update -name NAME [options] 326 327 Updates an auth method. By default it will merge the auth method 328 information with its current state so that you do not have to provide all 329 parameters. This behavior can be disabled by passing -no-merge. 330 331 Update all editable fields of the auth method: 332 333 $ consul acl auth-method update -name "my-k8s" \ 334 -description "new description" \ 335 -kubernetes-host "https://new-apiserver.example.com:8443" \ 336 -kubernetes-ca-file /path/to/new-kube.ca.crt \ 337 -kubernetes-service-account-jwt "NEW_JWT_CONTENTS" 338` 339