github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/controller/register.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package controller 5 6 import ( 7 "bufio" 8 "bytes" 9 "crypto/rand" 10 "encoding/asn1" 11 "encoding/base64" 12 "encoding/json" 13 "fmt" 14 "io" 15 "net/http" 16 "os" 17 "strings" 18 19 "github.com/juju/cmd" 20 "github.com/juju/errors" 21 "github.com/juju/names" 22 "github.com/juju/utils" 23 "github.com/juju/utils/set" 24 "golang.org/x/crypto/nacl/secretbox" 25 "golang.org/x/crypto/ssh/terminal" 26 27 "github.com/juju/juju/api" 28 "github.com/juju/juju/apiserver/params" 29 "github.com/juju/juju/cmd/modelcmd" 30 "github.com/juju/juju/jujuclient" 31 ) 32 33 var errNoModels = errors.New(` 34 There are no models available. You can create models with 35 "juju create-model", or you can ask an administrator or owner 36 of a model to grant access to that model with "juju grant".`[1:]) 37 38 // NewRegisterCommand returns a command to allow the user to register a controller. 39 func NewRegisterCommand() cmd.Command { 40 cmd := ®isterCommand{} 41 cmd.apiOpen = cmd.APIOpen 42 cmd.refreshModels = cmd.RefreshModels 43 cmd.store = jujuclient.NewFileClientStore() 44 return modelcmd.WrapBase(cmd) 45 } 46 47 // registerCommand logs in to a Juju controller and caches the connection 48 // information. 49 type registerCommand struct { 50 modelcmd.JujuCommandBase 51 apiOpen api.OpenFunc 52 refreshModels func(_ jujuclient.ClientStore, controller, account string) error 53 store jujuclient.ClientStore 54 EncodedData string 55 } 56 57 var usageRegisterSummary = ` 58 Registers a Juju user to a controller.`[1:] 59 60 var usageRegisterDetails = ` 61 Connects to a controller and completes the user registration process that 62 began with the `[1:] + "`juju add-user`" + ` command. The latter prints out the 'string' 63 that is referred to in Usage. 64 The user will be prompted for a password, which, once set, causes the 65 registration string to be voided. In order to start using Juju the user 66 can now either create a model or wait for a model to be shared with them. 67 Some machine providers will require the user to be in possession of 68 certain credentials in order to create a model. 69 70 Examples: 71 72 juju register MFATA3JvZDAnExMxMDQuMTU0LjQyLjQ0OjE3MDcwExAxMC4xMjguMC4yOjE3MDcw 73 BCBEFCaXerhNImkKKabuX5ULWf2Bp4AzPNJEbXVWgraLrAA= 74 75 See also: 76 add-user 77 change-user-password` 78 79 // Info implements Command.Info 80 func (c *registerCommand) Info() *cmd.Info { 81 return &cmd.Info{ 82 Name: "register", 83 Args: "<string>", 84 Purpose: usageRegisterSummary, 85 Doc: usageRegisterDetails, 86 } 87 } 88 89 // SetFlags implements Command.Init. 90 func (c *registerCommand) Init(args []string) error { 91 if len(args) < 1 { 92 return errors.New("registration data missing") 93 } 94 c.EncodedData, args = args[0], args[1:] 95 if err := cmd.CheckEmpty(args); err != nil { 96 return err 97 } 98 return nil 99 } 100 101 func (c *registerCommand) Run(ctx *cmd.Context) error { 102 103 registrationParams, err := c.getParameters(ctx) 104 if err != nil { 105 return errors.Trace(err) 106 } 107 _, err = c.store.ControllerByName(registrationParams.controllerName) 108 if err == nil { 109 return errors.AlreadyExistsf("controller %q", registrationParams.controllerName) 110 } else if !errors.IsNotFound(err) { 111 return errors.Trace(err) 112 } 113 114 // During registration we must set a new password. This has to be done 115 // atomically with the clearing of the secret key. 116 payloadBytes, err := json.Marshal(params.SecretKeyLoginRequestPayload{ 117 registrationParams.newPassword, 118 }) 119 if err != nil { 120 return errors.Trace(err) 121 } 122 123 // Make the registration call. 124 req := params.SecretKeyLoginRequest{ 125 Nonce: registrationParams.nonce[:], 126 User: registrationParams.userTag.String(), 127 PayloadCiphertext: secretbox.Seal( 128 nil, payloadBytes, 129 ®istrationParams.nonce, 130 ®istrationParams.key, 131 ), 132 } 133 resp, err := c.secretKeyLogin(registrationParams.controllerAddrs, req) 134 if err != nil { 135 return errors.Trace(err) 136 } 137 138 // Decrypt the response to authenticate the controller and 139 // obtain its CA certificate. 140 if len(resp.Nonce) != len(registrationParams.nonce) { 141 return errors.NotValidf("response nonce") 142 } 143 var respNonce [24]byte 144 copy(respNonce[:], resp.Nonce) 145 payloadBytes, ok := secretbox.Open(nil, resp.PayloadCiphertext, &respNonce, ®istrationParams.key) 146 if !ok { 147 return errors.NotValidf("response payload") 148 } 149 var responsePayload params.SecretKeyLoginResponsePayload 150 if err := json.Unmarshal(payloadBytes, &responsePayload); err != nil { 151 return errors.Annotate(err, "unmarshalling response payload") 152 } 153 154 // Store the controller and account details. 155 controllerDetails := jujuclient.ControllerDetails{ 156 APIEndpoints: registrationParams.controllerAddrs, 157 ControllerUUID: responsePayload.ControllerUUID, 158 CACert: responsePayload.CACert, 159 } 160 if err := c.store.UpdateController(registrationParams.controllerName, controllerDetails); err != nil { 161 return errors.Trace(err) 162 } 163 macaroonJSON, err := responsePayload.Macaroon.MarshalJSON() 164 if err != nil { 165 return errors.Annotate(err, "marshalling temporary credential to JSON") 166 } 167 accountDetails := jujuclient.AccountDetails{ 168 User: registrationParams.userTag.Canonical(), 169 Macaroon: string(macaroonJSON), 170 } 171 accountName := accountDetails.User 172 if err := c.store.UpdateAccount( 173 registrationParams.controllerName, accountName, accountDetails, 174 ); err != nil { 175 return errors.Trace(err) 176 } 177 if err := c.store.SetCurrentAccount( 178 registrationParams.controllerName, accountName, 179 ); err != nil { 180 return errors.Trace(err) 181 } 182 183 // Log into the controller to verify the credentials, and 184 // refresh the connection information. 185 if err := c.refreshModels(c.store, registrationParams.controllerName, accountName); err != nil { 186 return errors.Trace(err) 187 } 188 if err := modelcmd.WriteCurrentController(registrationParams.controllerName); err != nil { 189 return errors.Trace(err) 190 } 191 192 fmt.Fprintf( 193 ctx.Stderr, "\nWelcome, %s. You are now logged into %q.\n", 194 registrationParams.userTag.Id(), registrationParams.controllerName, 195 ) 196 return c.maybeSetCurrentModel(ctx, registrationParams.controllerName, accountName) 197 } 198 199 func (c *registerCommand) maybeSetCurrentModel(ctx *cmd.Context, controllerName, accountName string) error { 200 models, err := c.store.AllModels(controllerName, accountName) 201 if errors.IsNotFound(err) { 202 fmt.Fprintf(ctx.Stderr, "\n%s\n\n", errNoModels.Error()) 203 return nil 204 } else if err != nil { 205 return errors.Trace(err) 206 } 207 208 // If we get to here, there is at least one model. 209 if len(models) == 1 { 210 // There is exactly one model shared, 211 // so set it as the current model. 212 var modelName string 213 for modelName = range models { 214 // Loop exists only to obtain one and only key. 215 } 216 err := c.store.SetCurrentModel(controllerName, accountName, modelName) 217 if err != nil { 218 return errors.Trace(err) 219 } 220 fmt.Fprintf(ctx.Stderr, "\nCurrent model set to %q\n\n", modelName) 221 } else { 222 fmt.Fprintf(ctx.Stderr, ` 223 There are %d models available. Use "juju switch" to select 224 one of them: 225 `, len(models)) 226 modelNames := make(set.Strings) 227 for modelName := range models { 228 modelNames.Add(modelName) 229 } 230 for _, modelName := range modelNames.SortedValues() { 231 fmt.Fprintf(ctx.Stderr, " - juju switch %s\n", modelName) 232 } 233 fmt.Fprintln(ctx.Stderr) 234 } 235 return nil 236 } 237 238 type registrationParams struct { 239 userTag names.UserTag 240 controllerName string 241 controllerAddrs []string 242 key [32]byte 243 nonce [24]byte 244 newPassword string 245 } 246 247 // getParameters gets all of the parameters required for registering, prompting 248 // the user as necessary. 249 func (c *registerCommand) getParameters(ctx *cmd.Context) (*registrationParams, error) { 250 251 // Decode key, username, controller addresses from the string supplied 252 // on the command line. 253 decodedData, err := base64.URLEncoding.DecodeString(c.EncodedData) 254 if err != nil { 255 return nil, errors.Trace(err) 256 } 257 var info jujuclient.RegistrationInfo 258 if _, err := asn1.Unmarshal(decodedData, &info); err != nil { 259 return nil, errors.Trace(err) 260 } 261 262 params := registrationParams{ 263 controllerAddrs: info.Addrs, 264 userTag: names.NewUserTag(info.User), 265 } 266 if len(info.SecretKey) != len(params.key) { 267 return nil, errors.NotValidf("secret key") 268 } 269 copy(params.key[:], info.SecretKey) 270 271 // Prompt the user for the controller name. 272 controllerName, err := c.promptControllerName(ctx.Stderr, ctx.Stdin) 273 if err != nil { 274 return nil, errors.Trace(err) 275 } 276 params.controllerName = controllerName 277 278 // Prompt the user for the new password to set. 279 newPassword, err := c.promptNewPassword(ctx.Stderr, ctx.Stdin) 280 if err != nil { 281 return nil, errors.Trace(err) 282 } 283 params.newPassword = newPassword 284 285 // Generate a random nonce for encrypting the request. 286 if _, err := rand.Read(params.nonce[:]); err != nil { 287 return nil, errors.Trace(err) 288 } 289 290 return ¶ms, nil 291 } 292 293 func (c *registerCommand) secretKeyLogin(addrs []string, request params.SecretKeyLoginRequest) (*params.SecretKeyLoginResponse, error) { 294 buf, err := json.Marshal(&request) 295 if err != nil { 296 return nil, errors.Annotate(err, "marshalling request") 297 } 298 r := bytes.NewReader(buf) 299 300 // Determine which address to use by attempting to open an API 301 // connection with each of the addresses. Note that we do not 302 // know the CA certificate yet, so we do not want to send any 303 // sensitive information. We make no attempt to log in until 304 // we can verify the server's identity. 305 opts := api.DefaultDialOpts() 306 opts.InsecureSkipVerify = true 307 conn, err := c.apiOpen(&api.Info{ 308 Addrs: addrs, 309 SkipLogin: true, 310 // NOTE(axw) CACert is required, but ignored if 311 // InsecureSkipVerify is set. We should try to 312 // bring together CACert and InsecureSkipVerify 313 // so they can be validated together. 314 CACert: "ignored", 315 }, opts) 316 if err != nil { 317 return nil, errors.Trace(err) 318 } 319 apiAddr := conn.Addr() 320 if err := conn.Close(); err != nil { 321 return nil, errors.Trace(err) 322 } 323 324 // Using the address we connected to above, perform the request. 325 urlString := fmt.Sprintf("https://%s/register", apiAddr) 326 httpReq, err := http.NewRequest("POST", urlString, r) 327 if err != nil { 328 return nil, errors.Trace(err) 329 } 330 httpReq.Header.Set("Content-Type", "application/json") 331 httpClient := utils.GetNonValidatingHTTPClient() 332 httpResp, err := httpClient.Do(httpReq) 333 if err != nil { 334 return nil, errors.Trace(err) 335 } 336 defer httpResp.Body.Close() 337 338 if httpResp.StatusCode != http.StatusOK { 339 var resp params.ErrorResult 340 if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil { 341 return nil, errors.Trace(err) 342 } 343 return nil, resp.Error 344 } 345 346 var resp params.SecretKeyLoginResponse 347 if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil { 348 return nil, errors.Trace(err) 349 } 350 return &resp, nil 351 } 352 353 func (c *registerCommand) promptNewPassword(stderr io.Writer, stdin io.Reader) (string, error) { 354 password, err := c.readPassword("Enter password: ", stderr, stdin) 355 if err != nil { 356 return "", errors.Trace(err) 357 } 358 if password == "" { 359 return "", errors.NewNotValid(nil, "you must specify a non-empty password") 360 } 361 passwordConfirmation, err := c.readPassword("Confirm password: ", stderr, stdin) 362 if err != nil { 363 return "", errors.Trace(err) 364 } 365 if password != passwordConfirmation { 366 return "", errors.Errorf("passwords do not match") 367 } 368 return password, nil 369 } 370 371 func (c *registerCommand) promptControllerName(stderr io.Writer, stdin io.Reader) (string, error) { 372 fmt.Fprintf(stderr, "Please set a name for this controller: ") 373 defer stderr.Write([]byte{'\n'}) 374 name, err := c.readLine(stdin) 375 if err != nil { 376 return "", errors.Trace(err) 377 } 378 name = strings.TrimSpace(name) 379 if name == "" { 380 return "", errors.NewNotValid(nil, "you must specify a non-empty controller name") 381 } 382 return name, nil 383 } 384 385 func (c *registerCommand) readPassword(prompt string, stderr io.Writer, stdin io.Reader) (string, error) { 386 fmt.Fprintf(stderr, "%s", prompt) 387 defer stderr.Write([]byte{'\n'}) 388 if f, ok := stdin.(*os.File); ok && terminal.IsTerminal(int(f.Fd())) { 389 password, err := terminal.ReadPassword(int(f.Fd())) 390 if err != nil { 391 return "", errors.Trace(err) 392 } 393 return string(password), nil 394 } 395 return c.readLine(stdin) 396 } 397 398 func (c *registerCommand) readLine(stdin io.Reader) (string, error) { 399 // Read one byte at a time to avoid reading beyond the delimiter. 400 line, err := bufio.NewReader(byteAtATimeReader{stdin}).ReadString('\n') 401 if err != nil { 402 return "", errors.Trace(err) 403 } 404 return line[:len(line)-1], nil 405 } 406 407 type byteAtATimeReader struct { 408 io.Reader 409 } 410 411 func (r byteAtATimeReader) Read(out []byte) (int, error) { 412 return r.Reader.Read(out[:1]) 413 }