github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/stateauthenticator/context.go (about)

     1  // Copyright 2015-2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package stateauthenticator
     5  
     6  import (
     7  	"net/http"
     8  	"net/url"
     9  	"sync"
    10  
    11  	"github.com/juju/clock"
    12  	"github.com/juju/errors"
    13  	"gopkg.in/juju/names.v2"
    14  	"gopkg.in/macaroon-bakery.v2-unstable/bakery"
    15  	"gopkg.in/macaroon-bakery.v2-unstable/bakery/checkers"
    16  	"gopkg.in/macaroon-bakery.v2-unstable/httpbakery"
    17  	"gopkg.in/macaroon.v2-unstable"
    18  
    19  	"github.com/juju/juju/apiserver/authentication"
    20  	"github.com/juju/juju/apiserver/bakeryutil"
    21  	"github.com/juju/juju/apiserver/common"
    22  	"github.com/juju/juju/apiserver/params"
    23  	"github.com/juju/juju/state"
    24  )
    25  
    26  const (
    27  	localUserIdentityLocationPath = "/auth"
    28  )
    29  
    30  // authContext holds authentication context shared
    31  // between all API endpoints.
    32  type authContext struct {
    33  	st *state.State
    34  
    35  	clock     clock.Clock
    36  	agentAuth authentication.AgentAuthenticator
    37  
    38  	// localUserBakeryService is the bakery.Service used by the controller
    39  	// for authenticating local users. In time, we may want to use this for
    40  	// both local and external users. Note that this service does not
    41  	// discharge the third-party caveats.
    42  	localUserBakeryService *bakeryutil.ExpirableStorageBakeryService
    43  
    44  	// localUserThirdPartyBakeryService is the bakery.Service used by the
    45  	// controller for discharging third-party caveats for local users.
    46  	localUserThirdPartyBakeryService *bakery.Service
    47  
    48  	// localUserInteractions maintains a set of in-progress local user
    49  	// authentication interactions.
    50  	localUserInteractions *authentication.Interactions
    51  
    52  	// macaroonAuthOnce guards the fields below it.
    53  	macaroonAuthOnce   sync.Once
    54  	_macaroonAuth      *authentication.ExternalMacaroonAuthenticator
    55  	_macaroonAuthError error
    56  }
    57  
    58  // newAuthContext creates a new authentication context for st.
    59  func newAuthContext(
    60  	st *state.State,
    61  	clock clock.Clock,
    62  ) (*authContext, error) {
    63  	ctxt := &authContext{
    64  		st:                    st,
    65  		clock:                 clock,
    66  		localUserInteractions: authentication.NewInteractions(),
    67  	}
    68  
    69  	// Create a bakery service for discharging third-party caveats for
    70  	// local user authentication. This service does not persist keys;
    71  	// its macaroons should be very short-lived.
    72  	localUserThirdPartyBakeryService, _, err := bakeryutil.NewBakeryService(st, nil, nil)
    73  	if err != nil {
    74  		return nil, errors.Trace(err)
    75  	}
    76  	ctxt.localUserThirdPartyBakeryService = localUserThirdPartyBakeryService
    77  
    78  	// Create a bakery service for local user authentication. This service
    79  	// persists keys into MongoDB in a TTL collection.
    80  	store, err := st.NewBakeryStorage()
    81  	if err != nil {
    82  		return nil, errors.Trace(err)
    83  	}
    84  	locator := bakeryutil.BakeryServicePublicKeyLocator{ctxt.localUserThirdPartyBakeryService}
    85  	localUserBakeryService, localUserBakeryServiceKey, err := bakeryutil.NewBakeryService(
    86  		st, store, locator,
    87  	)
    88  	if err != nil {
    89  		return nil, errors.Trace(err)
    90  	}
    91  	ctxt.localUserBakeryService = &bakeryutil.ExpirableStorageBakeryService{
    92  		localUserBakeryService, localUserBakeryServiceKey, store, locator,
    93  	}
    94  	return ctxt, nil
    95  }
    96  
    97  // CreateLocalLoginMacaroon creates a macaroon that may be provided to a user
    98  // as proof that they have logged in with a valid username and password. This
    99  // macaroon may then be used to obtain a discharge macaroon so that the user
   100  // can log in without presenting their password for a set amount of time.
   101  func (ctxt *authContext) CreateLocalLoginMacaroon(tag names.UserTag) (*macaroon.Macaroon, error) {
   102  	return authentication.CreateLocalLoginMacaroon(tag, ctxt.localUserThirdPartyBakeryService, ctxt.clock)
   103  }
   104  
   105  // CheckLocalLoginCaveat parses and checks that the given caveat string is
   106  // valid for a local login request, and returns the tag of the local user
   107  // that the caveat asserts is logged in. checkers.ErrCaveatNotRecognized will
   108  // be returned if the caveat is not recognised.
   109  func (ctxt *authContext) CheckLocalLoginCaveat(caveat string) (names.UserTag, error) {
   110  	return authentication.CheckLocalLoginCaveat(caveat)
   111  }
   112  
   113  // CheckLocalLoginRequest checks that the given HTTP request contains at least
   114  // one valid local login macaroon minted using CreateLocalLoginMacaroon. It
   115  // returns an error with a *bakery.VerificationError cause if the macaroon
   116  // verification failed. If the macaroon is valid, CheckLocalLoginRequest returns
   117  // a list of caveats to add to the discharge macaroon.
   118  func (ctxt *authContext) CheckLocalLoginRequest(req *http.Request, tag names.UserTag) ([]checkers.Caveat, error) {
   119  	return authentication.CheckLocalLoginRequest(ctxt.localUserThirdPartyBakeryService, req, tag, ctxt.clock)
   120  }
   121  
   122  // authenticator returns an authenticator.EntityAuthenticator for the API
   123  // connection associated with the specified API server host.
   124  func (ctxt *authContext) authenticator(serverHost string) authenticator {
   125  	return authenticator{ctxt: ctxt, serverHost: serverHost}
   126  }
   127  
   128  // authenticator implements authenticator.EntityAuthenticator, delegating
   129  // to the appropriate authenticator based on the tag kind.
   130  type authenticator struct {
   131  	ctxt       *authContext
   132  	serverHost string
   133  }
   134  
   135  // Authenticate implements authentication.EntityAuthenticator
   136  // by choosing the right kind of authentication for the given
   137  // tag.
   138  func (a authenticator) Authenticate(
   139  	entityFinder authentication.EntityFinder,
   140  	tag names.Tag,
   141  	req params.LoginRequest,
   142  ) (state.Entity, error) {
   143  	auth, err := a.authenticatorForTag(tag)
   144  	if err != nil {
   145  		return nil, errors.Trace(err)
   146  	}
   147  	return auth.Authenticate(entityFinder, tag, req)
   148  }
   149  
   150  // authenticatorForTag returns the authenticator appropriate
   151  // to use for a login with the given possibly-nil tag.
   152  func (a authenticator) authenticatorForTag(tag names.Tag) (authentication.EntityAuthenticator, error) {
   153  	if tag == nil {
   154  		auth, err := a.ctxt.externalMacaroonAuth()
   155  		if errors.Cause(err) == errMacaroonAuthNotConfigured {
   156  			err = errors.Trace(common.ErrNoCreds)
   157  		}
   158  		if err != nil {
   159  			return nil, errors.Trace(err)
   160  		}
   161  		return auth, nil
   162  	}
   163  	switch tag.Kind() {
   164  	case names.UnitTagKind, names.MachineTagKind, names.ApplicationTagKind:
   165  		return &a.ctxt.agentAuth, nil
   166  	case names.UserTagKind:
   167  		return a.localUserAuth(), nil
   168  	default:
   169  		return nil, errors.Annotatef(common.ErrBadRequest, "unexpected login entity tag")
   170  	}
   171  }
   172  
   173  // localUserAuth returns an authenticator that can authenticate logins for
   174  // local users with either passwords or macaroons.
   175  func (a authenticator) localUserAuth() *authentication.UserAuthenticator {
   176  	localUserIdentityLocation := url.URL{
   177  		Scheme: "https",
   178  		Host:   a.serverHost,
   179  		Path:   localUserIdentityLocationPath,
   180  	}
   181  	return &authentication.UserAuthenticator{
   182  		Service:                   a.ctxt.localUserBakeryService,
   183  		Clock:                     a.ctxt.clock,
   184  		LocalUserIdentityLocation: localUserIdentityLocation.String(),
   185  	}
   186  }
   187  
   188  // externalMacaroonAuth returns an authenticator that can authenticate macaroon-based
   189  // logins for external users. If it fails once, it will always fail.
   190  func (ctxt *authContext) externalMacaroonAuth() (authentication.EntityAuthenticator, error) {
   191  	ctxt.macaroonAuthOnce.Do(func() {
   192  		ctxt._macaroonAuth, ctxt._macaroonAuthError = newExternalMacaroonAuth(ctxt.st)
   193  	})
   194  	if ctxt._macaroonAuth == nil {
   195  		return nil, errors.Trace(ctxt._macaroonAuthError)
   196  	}
   197  	return ctxt._macaroonAuth, nil
   198  }
   199  
   200  var errMacaroonAuthNotConfigured = errors.New("macaroon authentication is not configured")
   201  
   202  // newExternalMacaroonAuth returns an authenticator that can authenticate
   203  // macaroon-based logins for external users. This is just a helper function
   204  // for authCtxt.externalMacaroonAuth.
   205  func newExternalMacaroonAuth(st *state.State) (*authentication.ExternalMacaroonAuthenticator, error) {
   206  	controllerCfg, err := st.ControllerConfig()
   207  	if err != nil {
   208  		return nil, errors.Annotate(err, "cannot get model config")
   209  	}
   210  	idURL := controllerCfg.IdentityURL()
   211  	if idURL == "" {
   212  		return nil, errMacaroonAuthNotConfigured
   213  	}
   214  	var locator bakery.PublicKeyLocator
   215  	// The identity server has been configured,
   216  	// so configure the bakery service appropriately.
   217  	idPK := controllerCfg.IdentityPublicKey()
   218  	if idPK != nil {
   219  		locator = bakery.PublicKeyLocatorMap{idURL: idPK}
   220  	} else {
   221  		// No public key supplied - retrieve it from the identity manager on demand.
   222  		// Note that we don't fetch it immediately because then we'll fail
   223  		// forever if the initial fetch fails (because newExternalMacaroonAuth
   224  		// only ever called once).
   225  		locator = httpbakery.NewPublicKeyRing(nil, nil)
   226  	}
   227  	// We pass in nil for the storage, which leads to in-memory storage
   228  	// being used. We only use in-memory storage for now, since we never
   229  	// expire the keys, and don't want garbage to accumulate.
   230  	//
   231  	// TODO(axw) we should store the key in mongo, so that multiple servers
   232  	// can authenticate. That will require that we encode the server's ID
   233  	// in the macaroon ID so that servers don't overwrite each others' keys.
   234  	svc, _, err := bakeryutil.NewBakeryService(st, nil, locator)
   235  	if err != nil {
   236  		return nil, errors.Annotate(err, "cannot make bakery service")
   237  	}
   238  	var auth authentication.ExternalMacaroonAuthenticator
   239  	auth.Service = svc
   240  	auth.Macaroon, err = svc.NewMacaroon(nil)
   241  	if err != nil {
   242  		return nil, errors.Annotate(err, "cannot make macaroon")
   243  	}
   244  	auth.IdentityLocation = idURL
   245  	return &auth, nil
   246  }