// NewKeyManagerAPI creates a new server-side keyupdater API end point. func NewKeyManagerAPI(st *state.State, resources *common.Resources, authorizer common.Authorizer) (*KeyManagerAPI, error) { // Only clients and environment managers can access the key manager service. if !authorizer.AuthClient() && !authorizer.AuthEnvironManager() { return nil, common.ErrPerm } // TODO(wallyworld) - replace stub with real canRead function // For now, only admins can read authorised ssh keys. canRead := func(_ string) bool { return authorizer.GetAuthTag() == adminUser } // TODO(wallyworld) - replace stub with real canWrite function // For now, only admins can write authorised ssh keys for users. // Machine agents can write the juju-system-key. canWrite := func(user string) bool { // Are we a machine agent writing the Juju system key. if user == config.JujuSystemKey { _, ismachinetag := authorizer.GetAuthTag().(names.MachineTag) return ismachinetag } // Are we writing the auth key for a user. if _, err := st.User(user); err != nil { return false } return authorizer.GetAuthTag() == adminUser } return &KeyManagerAPI{ state: st, resources: resources, authorizer: authorizer, canRead: canRead, canWrite: canWrite}, nil }
// NewKeyManagerAPI creates a new server-side keyupdater API end point. func NewKeyManagerAPI( st *state.State, resources *common.Resources, authorizer common.Authorizer, ) (*KeyManagerAPI, error) { // Only clients and environment managers can access the key manager service. if !authorizer.AuthClient() && !authorizer.AuthEnvironManager() { return nil, common.ErrPerm } // TODO(wallyworld) - replace stub with real canRead function // For now, only admins can read authorised ssh keys. getCanRead := func() (common.AuthFunc, error) { return func(_ string) bool { return authorizer.GetAuthTag() == adminUser }, nil } // TODO(wallyworld) - replace stub with real canWrite function // For now, only admins can write authorised ssh keys for users. // Machine agents can write the juju-system-key. getCanWrite := func() (common.AuthFunc, error) { return func(tag string) bool { // Are we a machine agent writing the Juju system key. if tag == config.JujuSystemKey { // TODO(dfc) this can never be false _, err := names.ParseMachineTag(authorizer.GetAuthTag().String()) return err == nil } // Are we writing the auth key for a user. if _, err := st.User(tag); err != nil { return false } return authorizer.GetAuthTag() == adminUser }, nil } return &KeyManagerAPI{ state: st, resources: resources, authorizer: authorizer, getCanRead: getCanRead, getCanWrite: getCanWrite}, nil }
// The client will POST to the "/register" endpoint with a JSON-encoded // params.SecretKeyLoginRequest. This contains the tag of the user they // are registering, a (supposedly) unique nonce, and a ciphertext which // is the result of concatenating the user and nonce values, and then // encrypting and authenticating them with the NaCl Secretbox algorithm. // // If the server can decrypt the ciphertext, then it knows the client // has the required secret key; thus they are authenticated. The client // does not have the CA certificate for communicating securely with the // server, and so must also authenticate the server. The server will // similarly generate a unique nonce and encrypt the response payload // using the same secret key as the client. If the client can decrypt // the payload, it knows the server has the required secret key; thus // it is also authenticated. // // NOTE(axw) it is important that the client and server choose their // own nonces, because reusing a nonce means that the key-stream can // be revealed. func (h *registerUserHandler) processPost(req *http.Request, st *state.State) ( names.UserTag, *params.SecretKeyLoginResponse, error, ) { failure := func(err error) (names.UserTag, *params.SecretKeyLoginResponse, error) { return names.UserTag{}, nil, err } data, err := ioutil.ReadAll(req.Body) if err != nil { return failure(err) } var loginRequest params.SecretKeyLoginRequest if err := json.Unmarshal(data, &loginRequest); err != nil { return failure(err) } // Basic validation: ensure that the request contains a valid user tag, // nonce, and ciphertext of the expected length. userTag, err := names.ParseUserTag(loginRequest.User) if err != nil { return failure(err) } if len(loginRequest.Nonce) != secretboxNonceLength { return failure(errors.NotValidf("nonce")) } // Decrypt the ciphertext with the user's secret key (if it has one). user, err := st.User(userTag) if err != nil { return failure(err) } if len(user.SecretKey()) != secretboxKeyLength { return failure(errors.NotFoundf("secret key for user %q", user.Name())) } var key [secretboxKeyLength]byte var nonce [secretboxNonceLength]byte copy(key[:], user.SecretKey()) copy(nonce[:], loginRequest.Nonce) payloadBytes, ok := secretbox.Open(nil, loginRequest.PayloadCiphertext, &nonce, &key) if !ok { // Cannot decrypt the ciphertext, which implies that the secret // key specified by the client is invalid. return failure(errors.NotValidf("secret key")) } // Unmarshal the request payload, which contains the new password to // set for the user. var requestPayload params.SecretKeyLoginRequestPayload if err := json.Unmarshal(payloadBytes, &requestPayload); err != nil { return failure(errors.Annotate(err, "cannot unmarshal payload")) } if err := user.SetPassword(requestPayload.Password); err != nil { return failure(errors.Annotate(err, "setting new password")) } // Respond with the CA-cert and password, encrypted again with the // secret key. responsePayload, err := h.getSecretKeyLoginResponsePayload(st, userTag) if err != nil { return failure(errors.Trace(err)) } payloadBytes, err = json.Marshal(responsePayload) if err != nil { return failure(errors.Trace(err)) } if _, err := rand.Read(nonce[:]); err != nil { return failure(errors.Trace(err)) } response := ¶ms.SecretKeyLoginResponse{ Nonce: nonce[:], PayloadCiphertext: secretbox.Seal(nil, payloadBytes, &nonce, &key), } return userTag, response, nil }