github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/registration.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver 5 6 import ( 7 "crypto/rand" 8 "encoding/json" 9 "io" 10 "net/http" 11 12 "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" 13 "github.com/juju/errors" 14 "github.com/juju/names/v5" 15 "golang.org/x/crypto/nacl/secretbox" 16 "gopkg.in/macaroon.v2" 17 18 "github.com/juju/juju/apiserver/common" 19 coremacaroon "github.com/juju/juju/core/macaroon" 20 "github.com/juju/juju/environs" 21 "github.com/juju/juju/rpc/params" 22 "github.com/juju/juju/state" 23 "github.com/juju/juju/state/stateenvirons" 24 ) 25 26 const ( 27 secretboxNonceLength = 24 28 secretboxKeyLength = 32 29 ) 30 31 // registerUserHandler is an http.Handler for the "/register" endpoint. This is 32 // used to complete a secure user registration process, and provide controller 33 // login credentials. 34 type registerUserHandler struct { 35 ctxt httpContext 36 } 37 38 // ServeHTTP implements the http.Handler interface. 39 func (h *registerUserHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 40 if req.Method != "POST" { 41 err := sendError(w, errors.MethodNotAllowedf("unsupported method: %q", req.Method)) 42 if err != nil { 43 logger.Errorf("%v", err) 44 } 45 return 46 } 47 st, err := h.ctxt.stateForRequestUnauthenticated(req) 48 if err != nil { 49 if err := sendError(w, err); err != nil { 50 logger.Errorf("%v", err) 51 } 52 return 53 } 54 defer st.Release() 55 userTag, response, err := h.processPost(req, st.State) 56 if err != nil { 57 if err := sendError(w, err); err != nil { 58 logger.Errorf("%v", err) 59 } 60 return 61 } 62 63 // Set a short-lived macaroon as a cookie on the response, 64 // which the client can use to obtain a discharge macaroon. 65 m, err := h.ctxt.srv.localMacaroonAuthenticator.CreateLocalLoginMacaroon(req.Context(), userTag, httpbakery.RequestVersion(req)) 66 if err != nil { 67 if err := sendError(w, err); err != nil { 68 logger.Errorf("%v", err) 69 } 70 return 71 } 72 cookie, err := httpbakery.NewCookie(coremacaroon.MacaroonNamespace, macaroon.Slice{m}) 73 if err != nil { 74 if err := sendError(w, err); err != nil { 75 logger.Errorf("%v", err) 76 } 77 return 78 } 79 http.SetCookie(w, cookie) 80 81 if err := sendStatusAndJSON(w, http.StatusOK, response); err != nil { 82 logger.Errorf("%v", err) 83 } 84 } 85 86 // The client will POST to the "/register" endpoint with a JSON-encoded 87 // params.SecretKeyLoginRequest. This contains the tag of the user they 88 // are registering, a (supposedly) unique nonce, and a ciphertext which 89 // is the result of concatenating the user and nonce values, and then 90 // encrypting and authenticating them with the NaCl Secretbox algorithm. 91 // 92 // If the server can decrypt the ciphertext, then it knows the client 93 // has the required secret key; thus they are authenticated. The client 94 // does not have the CA certificate for communicating securely with the 95 // server, and so must also authenticate the server. The server will 96 // similarly generate a unique nonce and encrypt the response payload 97 // using the same secret key as the client. If the client can decrypt 98 // the payload, it knows the server has the required secret key; thus 99 // it is also authenticated. 100 // 101 // NOTE(axw) it is important that the client and server choose their 102 // own nonces, because reusing a nonce means that the key-stream can 103 // be revealed. 104 func (h *registerUserHandler) processPost(req *http.Request, st *state.State) ( 105 names.UserTag, *params.SecretKeyLoginResponse, error, 106 ) { 107 108 failure := func(err error) (names.UserTag, *params.SecretKeyLoginResponse, error) { 109 return names.UserTag{}, nil, err 110 } 111 112 data, err := io.ReadAll(req.Body) 113 if err != nil { 114 return failure(err) 115 } 116 var loginRequest params.SecretKeyLoginRequest 117 if err := json.Unmarshal(data, &loginRequest); err != nil { 118 return failure(err) 119 } 120 121 // Basic validation: ensure that the request contains a valid user tag, 122 // nonce, and ciphertext of the expected length. 123 userTag, err := names.ParseUserTag(loginRequest.User) 124 if err != nil { 125 return failure(err) 126 } 127 if len(loginRequest.Nonce) != secretboxNonceLength { 128 return failure(errors.NotValidf("nonce")) 129 } 130 131 // Decrypt the ciphertext with the user's secret key (if it has one). 132 user, err := st.User(userTag) 133 if err != nil { 134 return failure(err) 135 } 136 if len(user.SecretKey()) != secretboxKeyLength { 137 return failure(errors.NotFoundf("secret key for user %q", user.Name())) 138 } 139 var key [secretboxKeyLength]byte 140 var nonce [secretboxNonceLength]byte 141 copy(key[:], user.SecretKey()) 142 copy(nonce[:], loginRequest.Nonce) 143 payloadBytes, ok := secretbox.Open(nil, loginRequest.PayloadCiphertext, &nonce, &key) 144 if !ok { 145 // Cannot decrypt the ciphertext, which implies that the secret 146 // key specified by the client is invalid. 147 return failure(errors.NotValidf("secret key")) 148 } 149 150 // Unmarshal the request payload, which contains the new password to 151 // set for the user. 152 var requestPayload params.SecretKeyLoginRequestPayload 153 if err := json.Unmarshal(payloadBytes, &requestPayload); err != nil { 154 return failure(errors.Annotate(err, "cannot unmarshal payload")) 155 } 156 if err := user.SetPassword(requestPayload.Password); err != nil { 157 return failure(errors.Annotate(err, "setting new password")) 158 } 159 160 // Respond with the CA-cert and password, encrypted again with the 161 // secret key. 162 responsePayload, err := h.getSecretKeyLoginResponsePayload(st, userTag) 163 if err != nil { 164 return failure(errors.Trace(err)) 165 } 166 payloadBytes, err = json.Marshal(responsePayload) 167 if err != nil { 168 return failure(errors.Trace(err)) 169 } 170 if _, err := rand.Read(nonce[:]); err != nil { 171 return failure(errors.Trace(err)) 172 } 173 response := ¶ms.SecretKeyLoginResponse{ 174 Nonce: nonce[:], 175 PayloadCiphertext: secretbox.Seal(nil, payloadBytes, &nonce, &key), 176 } 177 return userTag, response, nil 178 } 179 180 func getConnectorInfoer(model stateenvirons.Model) (environs.ConnectorInfo, error) { 181 configGetter := stateenvirons.EnvironConfigGetter{Model: model} 182 environ, err := common.EnvironFuncForModel(model, configGetter)() 183 if err != nil { 184 return nil, errors.Trace(err) 185 } 186 if connInfo, ok := environ.(environs.ConnectorInfo); ok { 187 return connInfo, nil 188 } 189 return nil, errors.NotSupportedf("environ %q", environ.Config().Type()) 190 } 191 192 // For testing. 193 var GetConnectorInfoer = getConnectorInfoer 194 195 // getSecretKeyLoginResponsePayload returns the information required by the 196 // client to login to the controller securely. 197 func (h *registerUserHandler) getSecretKeyLoginResponsePayload( 198 st *state.State, userTag names.UserTag, 199 ) (*params.SecretKeyLoginResponsePayload, error) { 200 if !st.IsController() { 201 return nil, errors.New("state is not for a controller") 202 } 203 controllerConfig, err := st.ControllerConfig() 204 if err != nil { 205 return nil, errors.Trace(err) 206 } 207 caCert, _ := controllerConfig.CACert() 208 payload := params.SecretKeyLoginResponsePayload{ 209 CACert: caCert, 210 ControllerUUID: st.ControllerUUID(), 211 } 212 213 model, err := st.Model() 214 if err != nil { 215 return nil, errors.Trace(err) 216 } 217 connInfo, err := GetConnectorInfoer(model) 218 if errors.Is(err, errors.NotSupported) { // Not all providers support this. 219 return &payload, nil 220 } 221 if err != nil { 222 return nil, errors.Trace(err) 223 } 224 proxier, err := connInfo.ConnectionProxyInfo() 225 if errors.Is(err, errors.NotFound) { 226 return &payload, nil 227 } 228 if err != nil { 229 return nil, errors.Trace(err) 230 } 231 if payload.ProxyConfig, err = params.NewProxy(proxier); err != nil { 232 return nil, errors.Trace(err) 233 } 234 return &payload, nil 235 }