github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/authcontext.go (about)

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