github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/stateauthenticator/context_test.go (about) 1 // Copyright 2018 Canonical Ltd. All rights reserved. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package stateauthenticator_test 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery" 11 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers" 12 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/identchecker" 13 "github.com/go-macaroon-bakery/macaroon-bakery/v3/bakerytest" 14 "github.com/go-macaroon-bakery/macaroon-bakery/v3/httpbakery" 15 "github.com/juju/clock/testclock" 16 "github.com/juju/errors" 17 jc "github.com/juju/testing/checkers" 18 gc "gopkg.in/check.v1" 19 "gopkg.in/macaroon.v2" 20 21 "github.com/juju/juju/apiserver/stateauthenticator" 22 "github.com/juju/juju/controller" 23 statetesting "github.com/juju/juju/state/testing" 24 ) 25 26 // TODO(babbageclunk): These have been extracted pretty mechanically 27 // from the API server tests as part of the apiserver/httpserver 28 // split. They should be updated to test via the public interface 29 // rather than the export_test functions. 30 31 type macaroonCommonSuite struct { 32 statetesting.StateSuite 33 discharger *bakerytest.Discharger 34 authenticator *stateauthenticator.Authenticator 35 clock *testclock.Clock 36 } 37 38 func (s *macaroonCommonSuite) SetUpTest(c *gc.C) { 39 s.StateSuite.SetUpTest(c) 40 s.clock = testclock.NewClock(time.Now()) 41 authenticator, err := stateauthenticator.NewAuthenticator(s.StatePool, s.clock) 42 c.Assert(err, jc.ErrorIsNil) 43 s.authenticator = authenticator 44 } 45 46 func (s *macaroonCommonSuite) TearDownTest(c *gc.C) { 47 if s.discharger != nil { 48 s.discharger.Close() 49 } 50 s.StateSuite.TearDownTest(c) 51 } 52 53 type macaroonAuthSuite struct { 54 macaroonCommonSuite 55 } 56 57 var _ = gc.Suite(&macaroonAuthSuite{}) 58 59 func (s *macaroonAuthSuite) SetUpTest(c *gc.C) { 60 s.discharger = bakerytest.NewDischarger(nil) 61 s.ControllerConfig = map[string]interface{}{ 62 controller.IdentityURL: s.discharger.Location(), 63 } 64 s.macaroonCommonSuite.SetUpTest(c) 65 } 66 67 type alwaysIdent struct { 68 IdentityLocation string 69 } 70 71 // IdentityFromContext implements IdentityClient.IdentityFromContext. 72 func (m *alwaysIdent) IdentityFromContext(ctx context.Context) (identchecker.Identity, []checkers.Caveat, error) { 73 return identchecker.SimpleIdentity("fred"), nil, nil 74 } 75 76 func (alwaysIdent) DeclaredIdentity(ctx context.Context, declared map[string]string) (identchecker.Identity, error) { 77 return nil, errors.New("not called") 78 } 79 80 func (s *macaroonAuthSuite) TestServerBakery(c *gc.C) { 81 // TODO - remove when we use bakeryv2 everywhere 82 discharger := bakerytest.NewDischarger(nil) 83 defer discharger.Close() 84 discharger.CheckerP = httpbakery.ThirdPartyCaveatCheckerPFunc(func(ctx context.Context, p httpbakery.ThirdPartyCaveatCheckerParams) ([]checkers.Caveat, error) { 85 if p.Caveat != nil && string(p.Caveat.Condition) == "is-authenticated-user" { 86 return []checkers.Caveat{ 87 checkers.DeclaredCaveat("username", "fred"), 88 }, nil 89 } 90 return nil, errors.New("unexpected caveat") 91 }) 92 93 bsvc, err := stateauthenticator.ServerBakery(s.authenticator, &alwaysIdent{discharger.Location()}) 94 c.Assert(err, gc.IsNil) 95 96 cav := []checkers.Caveat{ 97 checkers.NeedDeclaredCaveat( 98 checkers.Caveat{ 99 Location: discharger.Location(), 100 Condition: "is-authenticated-user", 101 }, 102 "username", 103 ), 104 } 105 mac, err := bsvc.Oven.NewMacaroon(context.Background(), bakery.LatestVersion, cav, bakery.NoOp) 106 c.Assert(err, gc.IsNil) 107 108 client := httpbakery.NewClient() 109 ms, err := client.DischargeAll(context.Background(), mac) 110 c.Assert(err, jc.ErrorIsNil) 111 112 _, cond, err := bsvc.Oven.VerifyMacaroon(context.Background(), ms) 113 c.Assert(err, gc.IsNil) 114 c.Assert(cond, jc.DeepEquals, []string{"declared username fred"}) 115 authChecker := bsvc.Checker.Auth(ms) 116 ai, err := authChecker.Allow(context.Background(), identchecker.LoginOp) 117 c.Assert(err, gc.IsNil) 118 c.Assert(ai.Identity.Id(), gc.Equals, "fred") 119 } 120 121 func (s *macaroonAuthSuite) TestExpiredKey(c *gc.C) { 122 bsvc, err := stateauthenticator.ServerBakeryExpiresImmediately(s.authenticator, &alwaysIdent{}) 123 c.Assert(err, gc.IsNil) 124 125 cav := []checkers.Caveat{ 126 checkers.NeedDeclaredCaveat( 127 checkers.Caveat{ 128 Condition: "is-authenticated-user", 129 }, 130 "username", 131 ), 132 } 133 mac, err := bsvc.Oven.NewMacaroon(context.Background(), bakery.LatestVersion, cav, bakery.NoOp) 134 c.Assert(err, gc.IsNil) 135 136 client := httpbakery.NewClient() 137 ms, err := client.DischargeAll(context.Background(), mac) 138 c.Assert(err, jc.ErrorIsNil) 139 140 _, _, err = bsvc.Oven.VerifyMacaroon(context.Background(), ms) 141 c.Assert(err, gc.ErrorMatches, "verification failed: macaroon not found in storage") 142 } 143 144 type macaroonAuthWrongPublicKeySuite struct { 145 macaroonCommonSuite 146 } 147 148 var _ = gc.Suite(&macaroonAuthWrongPublicKeySuite{}) 149 150 func (s *macaroonAuthWrongPublicKeySuite) SetUpTest(c *gc.C) { 151 s.discharger = bakerytest.NewDischarger(nil) 152 wrongKey, err := bakery.GenerateKey() 153 c.Assert(err, gc.IsNil) 154 s.ControllerConfig = map[string]interface{}{ 155 controller.IdentityURL: s.discharger.Location(), 156 controller.IdentityPublicKey: wrongKey.Public.String(), 157 } 158 s.macaroonCommonSuite.SetUpTest(c) 159 } 160 161 func (s *macaroonAuthWrongPublicKeySuite) TearDownTest(c *gc.C) { 162 s.discharger.Close() 163 s.StateSuite.TearDownTest(c) 164 } 165 166 func (s *macaroonAuthWrongPublicKeySuite) TestDischargeFailsWithWrongPublicKey(c *gc.C) { 167 ctx := context.Background() 168 client := httpbakery.NewClient() 169 170 m, err := macaroon.New(nil, nil, "loc", macaroon.LatestVersion) 171 c.Assert(err, jc.ErrorIsNil) 172 mac, err := bakery.NewLegacyMacaroon(m) 173 c.Assert(err, jc.ErrorIsNil) 174 cav := checkers.Caveat{ 175 Location: s.discharger.Location(), 176 Condition: "true", 177 } 178 anotherKey, err := bakery.GenerateKey() 179 c.Assert(err, jc.ErrorIsNil) 180 loc := bakery.NewThirdPartyStore() 181 loc.AddInfo(s.discharger.Location(), bakery.ThirdPartyInfo{}) 182 err = mac.AddCaveat(ctx, cav, anotherKey, loc) 183 c.Assert(err, jc.ErrorIsNil) 184 _, err = client.DischargeAll(ctx, mac) 185 c.Assert(err, gc.ErrorMatches, `cannot get discharge from ".*": third party refused discharge: cannot discharge: discharger cannot decode caveat id: public key mismatch`) 186 } 187 188 type macaroonNoURLSuite struct { 189 macaroonCommonSuite 190 } 191 192 var _ = gc.Suite(&macaroonNoURLSuite{}) 193 194 func (s *macaroonNoURLSuite) TestNoBakeryWhenNoIdentityURL(c *gc.C) { 195 // By default, when there is no identity location, no bakery is created. 196 _, err := stateauthenticator.ServerBakery(s.authenticator, nil) 197 c.Assert(err, gc.ErrorMatches, "macaroon authentication is not configured") 198 }