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 := &params.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  }