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  }