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 }