// writeError writes an error to w in response to req. If the error was // generated because of a required macaroon that the client does not // have, we mint a macaroon that, when discharged, will grant the client // the right to execute the given operation. // // The logic in this function is crucial to the security of the service // - it must determine for a given operation what caveats to attach. func (srv *targetServiceHandler) writeError(w http.ResponseWriter, req *http.Request, operation string, verr error) { log.Printf("writing error with operation %q", operation) fail := func(code int, msg string, args ...interface{}) { if code == http.StatusInternalServerError { msg = "internal error: " + msg } http.Error(w, fmt.Sprintf(msg, args...), code) } if _, ok := errgo.Cause(verr).(*bakery.VerificationError); !ok { fail(http.StatusForbidden, "%v", verr) return } // Work out what caveats we need to apply for the given operation. // Could special-case the operation here if desired. caveats := []checkers.Caveat{ checkers.TimeBeforeCaveat(time.Now().Add(5 * time.Minute)), { Location: srv.authEndpoint, Condition: "access-allowed", }, { Condition: "operation " + operation, }} // Mint an appropriate macaroon and send it back to the client. m, err := srv.svc.NewMacaroon("", nil, caveats) if err != nil { fail(http.StatusInternalServerError, "cannot mint macaroon: %v", err) return } httpbakery.WriteDischargeRequiredErrorForRequest(w, m, "", verr, req) }
func (s *ClientSuite) TestNewCookieExpires(c *gc.C) { t := time.Now().Add(24 * time.Hour) svc := newService("loc", nil) m, err := svc.NewMacaroon("", nil, []checkers.Caveat{ checkers.TimeBeforeCaveat(t), }) c.Assert(err, gc.IsNil) cookie, err := httpbakery.NewCookie(macaroon.Slice{m}) c.Assert(err, gc.IsNil) c.Assert(cookie.Expires.Equal(t), gc.Equals, true, gc.Commentf("obtained: %s, expected: %s", cookie.Expires, t)) }
// CreateLocalLoginMacaroon creates a macaroon that may be provided to a // user as proof that they have logged in with a valid username and password. // This macaroon may then be used to obtain a discharge macaroon so that // the user can log in without presenting their password for a set amount // of time. func CreateLocalLoginMacaroon( tag names.UserTag, service BakeryService, clock clock.Clock, ) (*macaroon.Macaroon, error) { // We create the macaroon with a random ID and random root key, which // enables multiple clients to login as the same user and obtain separate // macaroons without having them use the same root key. return service.NewMacaroon("", nil, []checkers.Caveat{ {Condition: "is-authenticated-user " + tag.Id()}, checkers.TimeBeforeCaveat(clock.Now().Add(LocalLoginInteractionTimeout)), }) }
func (u *UserAuthenticator) authenticateMacaroons( entityFinder EntityFinder, tag names.UserTag, req params.LoginRequest, ) (state.Entity, error) { // Check for a valid request macaroon. assert := map[string]string{usernameKey: tag.Id()} _, err := u.Service.CheckAny(req.Macaroons, assert, checkers.New(checkers.TimeBefore)) if err != nil { cause := err logger.Debugf("local-login macaroon authentication failed: %v", cause) if _, ok := errors.Cause(err).(*bakery.VerificationError); !ok { return nil, errors.Trace(err) } // The root keys for these macaroons are stored in MongoDB. // Expire the documents after after a set amount of time. expiryTime := u.Clock.Now().Add(localLoginExpiryTime) service, err := u.Service.ExpireStorageAt(expiryTime) if err != nil { return nil, errors.Trace(err) } m, err := service.NewMacaroon("", nil, []checkers.Caveat{ checkers.NeedDeclaredCaveat( checkers.Caveat{ Location: u.LocalUserIdentityLocation, Condition: "is-authenticated-user " + tag.Id(), }, usernameKey, ), checkers.TimeBeforeCaveat(expiryTime), }) if err != nil { return nil, errors.Annotate(err, "cannot create macaroon") } return nil, &common.DischargeRequiredError{ Cause: cause, Macaroon: m, } } entity, err := entityFinder.FindEntity(tag) if errors.IsNotFound(err) { logger.Debugf("entity %s not found", tag.String()) return nil, errors.Trace(common.ErrBadCreds) } else if err != nil { return nil, errors.Trace(err) } return entity, nil }
// CheckLocalLoginRequest checks that the given HTTP request contains at least // one valid local login macaroon minted by the given service using // CreateLocalLoginMacaroon. It returns an error with a // *bakery.VerificationError cause if the macaroon verification failed. If the // macaroon is valid, CheckLocalLoginRequest returns a list of caveats to add // to the discharge macaroon. func CheckLocalLoginRequest( service *bakery.Service, req *http.Request, tag names.UserTag, clock clock.Clock, ) ([]checkers.Caveat, error) { _, err := httpbakery.CheckRequest(service, req, nil, checkers.CheckerFunc{ // Having a macaroon with an is-authenticated-user // caveat is proof that the user is "logged in". "is-authenticated-user", func(cond, arg string) error { return nil }, }) if err != nil { return nil, errors.Trace(err) } firstPartyCaveats := []checkers.Caveat{ checkers.DeclaredCaveat("username", tag.Id()), checkers.TimeBeforeCaveat(clock.Now().Add(localLoginExpiryTime)), } return firstPartyCaveats, nil }
func (s *userAuthenticatorSuite) TestCreateLocalLoginMacaroon(c *gc.C) { service := mockBakeryService{} clock := coretesting.NewClock(time.Time{}) authenticator := &authentication.UserAuthenticator{ Service: &service, Clock: clock, } _, err := authenticator.CreateLocalLoginMacaroon(names.NewUserTag("bobbrown")) c.Assert(err, jc.ErrorIsNil) service.CheckCallNames(c, "ExpireStorageAt", "NewMacaroon", "AddCaveat") calls := service.Calls() c.Assert(calls[0].Args, jc.DeepEquals, []interface{}{clock.Now().Add(24 * time.Hour)}) c.Assert(calls[1].Args, jc.DeepEquals, []interface{}{ "", []byte(nil), []checkers.Caveat{ checkers.DeclaredCaveat("username", "bobbrown@local"), }, }) c.Assert(calls[2].Args, jc.DeepEquals, []interface{}{ &macaroon.Macaroon{}, checkers.TimeBeforeCaveat(clock.Now().Add(24 * time.Hour)), }) }
func (m *MacaroonAuthenticator) newDischargeRequiredError(cause error) error { if m.Service == nil || m.Macaroon == nil { return errors.Trace(cause) } mac := m.Macaroon.Clone() err := m.Service.AddCaveat(mac, checkers.TimeBeforeCaveat(time.Now().Add(time.Hour))) if err != nil { return errors.Annotatef(err, "cannot create macaroon") } err = m.Service.AddCaveat(mac, checkers.NeedDeclaredCaveat( checkers.Caveat{ Location: m.IdentityLocation, Condition: "is-authenticated-user", }, usernameKey, )) if err != nil { return errors.Annotatef(err, "cannot create macaroon") } return &common.DischargeRequiredError{ Cause: cause, Macaroon: mac, } }
func addMacaroonTimeBeforeCaveat(svc BakeryService, m *macaroon.Macaroon, t time.Time) error { return svc.AddCaveat(m, checkers.TimeBeforeCaveat(t)) }
}, { caveat: "a wrong", expectError: `caveat "a wrong" not satisfied: wrong arg`, expectCause: errgo.Is(errWrongArg), }, { caveat: "b wrong", expectError: `caveat "b wrong" not satisfied: wrong arg`, expectCause: errgo.Is(errWrongArg), }}, }, { about: "time within limit", checker: checkers.New( checkers.TimeBefore, ), checks: []checkTest{{ caveat: checkers.TimeBeforeCaveat(now.Add(1)).Condition, }, { caveat: checkers.TimeBeforeCaveat(now).Condition, expectError: `caveat "time-before 2006-01-02T15:04:05.123Z" not satisfied: macaroon has expired`, }, { caveat: checkers.TimeBeforeCaveat(now.Add(-1)).Condition, expectError: `caveat "time-before 2006-01-02T15:04:05.122999999Z" not satisfied: macaroon has expired`, }, { caveat: `time-before bad-date`, expectError: `caveat "time-before bad-date" not satisfied: parsing time "bad-date" as "2006-01-02T15:04:05.999999999Z07:00": cannot parse "bad-date" as "2006"`, }, { caveat: checkers.TimeBeforeCaveat(now).Condition + " ", expectError: `caveat "time-before 2006-01-02T15:04:05.123Z " not satisfied: parsing time "2006-01-02T15:04:05.123Z ": extra text: `, }}, }, { about: "declared, no entries",
func addMacaroonTimeBeforeCaveat(svc *bakery.Service, m *macaroon.Macaroon, d time.Duration) error { return svc.AddCaveat(m, checkers.TimeBeforeCaveat(time.Now().Add(d))) }
var expireTimeTests = []struct { about string caveats []macaroon.Caveat expectTime time.Time expectExpires bool }{{ about: "nil caveats", }, { about: "empty caveats", caveats: []macaroon.Caveat{}, }, { about: "single time-before caveat", caveats: []macaroon.Caveat{ macaroon.Caveat{ Id: checkers.TimeBeforeCaveat(t1).Condition, }, }, expectTime: t1, expectExpires: true, }, { about: "single deny caveat", caveats: []macaroon.Caveat{ macaroon.Caveat{ Id: checkers.DenyCaveat("abc").Condition, }, }, }, { about: "multiple time-before caveat", caveats: []macaroon.Caveat{ macaroon.Caveat{