github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/server_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"context"
     8  	"crypto/x509"
     9  	"fmt"
    10  	"net"
    11  	"net/http"
    12  
    13  	"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery"
    14  	"github.com/gorilla/websocket"
    15  	jujuerrors "github.com/juju/errors"
    16  	jujuhttp "github.com/juju/http/v2"
    17  	"github.com/juju/loggo"
    18  	"github.com/juju/names/v5"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/juju/utils/v3"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/macaroon.v2"
    23  
    24  	"github.com/juju/juju/api"
    25  	apimachiner "github.com/juju/juju/api/agent/machiner"
    26  	"github.com/juju/juju/apiserver"
    27  	"github.com/juju/juju/apiserver/authentication"
    28  	"github.com/juju/juju/apiserver/authentication/jwt"
    29  	"github.com/juju/juju/apiserver/errors"
    30  	apitesting "github.com/juju/juju/apiserver/testing"
    31  	"github.com/juju/juju/apiserver/testserver"
    32  	"github.com/juju/juju/core/network"
    33  	"github.com/juju/juju/core/permission"
    34  	jujutesting "github.com/juju/juju/juju/testing"
    35  	"github.com/juju/juju/rpc/params"
    36  	"github.com/juju/juju/state"
    37  	coretesting "github.com/juju/juju/testing"
    38  	"github.com/juju/juju/testing/factory"
    39  )
    40  
    41  var fastDialOpts = api.DialOpts{}
    42  
    43  type serverSuite struct {
    44  	jujutesting.JujuConnSuite
    45  }
    46  
    47  var _ = gc.Suite(&serverSuite{})
    48  
    49  func (s *serverSuite) TestStop(c *gc.C) {
    50  	// Start our own instance of the server so we have
    51  	// a handle on it to stop it.
    52  	srv := testserver.NewServer(c, s.StatePool, s.Controller)
    53  	defer assertStop(c, srv)
    54  
    55  	machine, password := s.Factory.MakeMachineReturningPassword(
    56  		c, &factory.MachineParams{Nonce: "fake_nonce"})
    57  
    58  	// Note we can't use openAs because we're not connecting to
    59  	info := srv.Info
    60  	info.Tag = machine.Tag()
    61  	info.Password = password
    62  	info.Nonce = "fake_nonce"
    63  	info.ModelTag = s.Model.ModelTag()
    64  
    65  	st, err := api.Open(info, fastDialOpts)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	defer st.Close()
    68  
    69  	_, err = apimachiner.NewState(st).Machine(machine.MachineTag())
    70  	c.Assert(err, jc.ErrorIsNil)
    71  
    72  	err = srv.Stop()
    73  	c.Assert(err, jc.ErrorIsNil)
    74  
    75  	_, err = apimachiner.NewState(st).Machine(machine.MachineTag())
    76  	// The client has not necessarily seen the server shutdown yet, so there
    77  	// are multiple possible errors. All we should care about is that there is
    78  	// an error, not what the error actually is.
    79  	c.Assert(err, gc.NotNil)
    80  
    81  	// Check it can be stopped twice.
    82  	err = srv.Stop()
    83  	c.Assert(err, jc.ErrorIsNil)
    84  }
    85  
    86  func (s *serverSuite) TestAPIServerCanListenOnBothIPv4AndIPv6(c *gc.C) {
    87  	err := s.State.SetAPIHostPorts(nil)
    88  	c.Assert(err, jc.ErrorIsNil)
    89  
    90  	// Start our own instance of the server listening on
    91  	// both IPv4 and IPv6 localhost addresses and an ephemeral port.
    92  	srv := testserver.NewServer(c, s.StatePool, s.Controller)
    93  	defer assertStop(c, srv)
    94  
    95  	machine, password := s.Factory.MakeMachineReturningPassword(
    96  		c, &factory.MachineParams{Nonce: "fake_nonce"})
    97  
    98  	info := srv.Info
    99  	port := info.Ports()[0]
   100  	portString := fmt.Sprintf("%d", port)
   101  
   102  	// Now connect twice - using IPv4 and IPv6 endpoints.
   103  	info.Tag = machine.Tag()
   104  	info.Password = password
   105  	info.Nonce = "fake_nonce"
   106  	info.ModelTag = s.Model.ModelTag()
   107  
   108  	ipv4State, err := api.Open(info, fastDialOpts)
   109  	c.Assert(err, jc.ErrorIsNil)
   110  	defer ipv4State.Close()
   111  	c.Assert(ipv4State.Addr(), gc.Equals, net.JoinHostPort("localhost", portString))
   112  	c.Assert(ipv4State.APIHostPorts(), jc.DeepEquals, []network.MachineHostPorts{
   113  		network.NewMachineHostPorts(port, "localhost"),
   114  	})
   115  
   116  	_, err = apimachiner.NewState(ipv4State).Machine(machine.MachineTag())
   117  	c.Assert(err, jc.ErrorIsNil)
   118  
   119  	info.Addrs = []string{net.JoinHostPort("::1", portString)}
   120  	ipv6State, err := api.Open(info, fastDialOpts)
   121  	c.Assert(err, jc.ErrorIsNil)
   122  	defer ipv6State.Close()
   123  	c.Assert(ipv6State.Addr(), gc.Equals, net.JoinHostPort("::1", portString))
   124  	c.Assert(ipv6State.APIHostPorts(), jc.DeepEquals, []network.MachineHostPorts{
   125  		network.NewMachineHostPorts(port, "::1"),
   126  	})
   127  
   128  	_, err = apimachiner.NewState(ipv6State).Machine(machine.MachineTag())
   129  	c.Assert(err, jc.ErrorIsNil)
   130  }
   131  
   132  func (s *serverSuite) TestOpenAsMachineErrors(c *gc.C) {
   133  	assertNotProvisioned := func(err error) {
   134  		c.Assert(err, gc.NotNil)
   135  		c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned)
   136  		c.Assert(err, gc.ErrorMatches, `machine \d+ not provisioned \(not provisioned\)`)
   137  	}
   138  
   139  	machine, password := s.Factory.MakeMachineReturningPassword(
   140  		c, &factory.MachineParams{Nonce: "fake_nonce"})
   141  
   142  	// This does almost exactly the same as OpenAPIAsMachine but checks
   143  	// for failures instead.
   144  	info := s.APIInfo(c)
   145  	info.Tag = machine.Tag()
   146  	info.Password = password
   147  	info.Nonce = "invalid-nonce"
   148  	st, err := api.Open(info, fastDialOpts)
   149  	assertNotProvisioned(err)
   150  	c.Assert(st, gc.IsNil)
   151  
   152  	// Try with empty nonce as well.
   153  	info.Nonce = ""
   154  	st, err = api.Open(info, fastDialOpts)
   155  	assertNotProvisioned(err)
   156  	c.Assert(st, gc.IsNil)
   157  
   158  	// Finally, with the correct one succeeds.
   159  	info.Nonce = "fake_nonce"
   160  	st, err = api.Open(info, fastDialOpts)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	c.Assert(st, gc.NotNil)
   163  	st.Close()
   164  
   165  	// Now add another machine, intentionally unprovisioned.
   166  	stm1, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	err = stm1.SetPassword(password)
   169  	c.Assert(err, jc.ErrorIsNil)
   170  
   171  	// Try connecting, it will fail.
   172  	info.Tag = stm1.Tag()
   173  	info.Nonce = ""
   174  	st, err = api.Open(info, fastDialOpts)
   175  	assertNotProvisioned(err)
   176  	c.Assert(st, gc.IsNil)
   177  }
   178  
   179  func dialWebsocket(c *gc.C, addr, path string) (*websocket.Conn, error) {
   180  	// TODO(rogpeppe) merge this with the very similar dialWebsocketFromURL function.
   181  	url := fmt.Sprintf("wss://%s%s", addr, path)
   182  	header := make(http.Header)
   183  	header.Set("Origin", "http://localhost/")
   184  	caCerts := x509.NewCertPool()
   185  	c.Assert(caCerts.AppendCertsFromPEM([]byte(coretesting.CACert)), jc.IsTrue)
   186  	tlsConfig := jujuhttp.SecureTLSConfig()
   187  	tlsConfig.RootCAs = caCerts
   188  	tlsConfig.ServerName = "anything"
   189  
   190  	dialer := &websocket.Dialer{
   191  		TLSClientConfig: tlsConfig,
   192  	}
   193  	conn, _, err := dialer.Dial(url, header)
   194  	return conn, err
   195  }
   196  
   197  func (s *serverSuite) TestNonCompatiblePathsAre404(c *gc.C) {
   198  	// We expose the API at '/api', '/' (controller-only), and at '/ModelUUID/api'
   199  	// for the correct location, but other paths should fail.
   200  	loggo.GetLogger("juju.apiserver").SetLogLevel(loggo.TRACE)
   201  	srv := testserver.NewServer(c, s.StatePool, s.Controller)
   202  	defer assertStop(c, srv)
   203  
   204  	// We have to use 'localhost' because that is what the TLS cert says.
   205  	addr := fmt.Sprintf("localhost:%d", srv.Info.Ports()[0])
   206  
   207  	// '/api' should be fine
   208  	conn, err := dialWebsocket(c, addr, "/api")
   209  	c.Assert(err, jc.ErrorIsNil)
   210  	conn.Close()
   211  
   212  	// '/`' should be fine
   213  	conn, err = dialWebsocket(c, addr, "/")
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	conn.Close()
   216  
   217  	// '/model/MODELUUID/api' should be fine
   218  	conn, err = dialWebsocket(c, addr, "/model/deadbeef-1234-5678-0123-0123456789ab/api")
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	conn.Close()
   221  
   222  	// '/randompath' is not ok
   223  	conn, err = dialWebsocket(c, addr, "/randompath")
   224  	// Unfortunately gorilla/websocket just returns bad handshake, it doesn't
   225  	// give us any information (whether this was a 404 Not Found, Internal
   226  	// Server Error, 200 OK, etc.)
   227  	c.Assert(err, gc.ErrorMatches, `websocket: bad handshake`)
   228  	c.Assert(conn, gc.IsNil)
   229  }
   230  
   231  type fakeResource struct {
   232  	stopped bool
   233  }
   234  
   235  func (r *fakeResource) Stop() error {
   236  	r.stopped = true
   237  	return nil
   238  }
   239  
   240  func (s *serverSuite) bootstrapHasPermissionTest(c *gc.C) (*state.User, names.ControllerTag) {
   241  	u, err := s.State.AddUser("foobar", "Foo Bar", "password", "read")
   242  	c.Assert(err, jc.ErrorIsNil)
   243  	user := u.UserTag()
   244  
   245  	ctag, err := names.ParseControllerTag("controller-" + s.State.ControllerUUID())
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	access, err := s.State.UserPermission(user, ctag)
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	c.Assert(access, gc.Equals, permission.LoginAccess)
   250  	return u, ctag
   251  }
   252  
   253  func (s *serverSuite) TestAPIHandlerHasPermissionLogin(c *gc.C) {
   254  	u, ctag := s.bootstrapHasPermissionTest(c)
   255  
   256  	handler, _ := apiserver.TestingAPIHandlerWithEntity(c, s.StatePool, s.State, u)
   257  	defer handler.Kill()
   258  
   259  	apiserver.AssertHasPermission(c, handler, permission.LoginAccess, ctag, true)
   260  	apiserver.AssertHasPermission(c, handler, permission.SuperuserAccess, ctag, false)
   261  }
   262  
   263  func (s *serverSuite) TestAPIHandlerHasPermissionSuperUser(c *gc.C) {
   264  	u, ctag := s.bootstrapHasPermissionTest(c)
   265  	user := u.UserTag()
   266  
   267  	handler, _ := apiserver.TestingAPIHandlerWithEntity(c, s.StatePool, s.State, u)
   268  	defer handler.Kill()
   269  
   270  	ua, err := s.State.SetUserAccess(user, ctag, permission.SuperuserAccess)
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	c.Assert(ua.Access, gc.Equals, permission.SuperuserAccess)
   273  
   274  	apiserver.AssertHasPermission(c, handler, permission.LoginAccess, ctag, true)
   275  	apiserver.AssertHasPermission(c, handler, permission.SuperuserAccess, ctag, true)
   276  }
   277  
   278  func (s *serverSuite) TestAPIHandlerHasPermissionLoginToken(c *gc.C) {
   279  	user := names.NewUserTag("fred")
   280  	token, err := apitesting.NewJWT(apitesting.JWTParams{
   281  		Controller: coretesting.ControllerTag.Id(),
   282  		User:       user.String(),
   283  		Access: map[string]string{
   284  			coretesting.ControllerTag.String(): "superuser",
   285  			coretesting.ModelTag.String():      "write",
   286  		},
   287  	})
   288  	c.Assert(err, jc.ErrorIsNil)
   289  
   290  	delegator := &jwt.PermissionDelegator{token}
   291  	handler, _ := apiserver.TestingAPIHandlerWithToken(c, s.StatePool, s.State, token, delegator)
   292  	defer handler.Kill()
   293  
   294  	apiserver.AssertHasPermission(c, handler, permission.LoginAccess, coretesting.ControllerTag, true)
   295  	apiserver.AssertHasPermission(c, handler, permission.SuperuserAccess, coretesting.ControllerTag, true)
   296  	apiserver.AssertHasPermission(c, handler, permission.WriteAccess, coretesting.ModelTag, true)
   297  }
   298  
   299  func (s *serverSuite) TestAPIHandlerMissingPermissionLoginToken(c *gc.C) {
   300  	user := names.NewUserTag("fred")
   301  	token, err := apitesting.NewJWT(apitesting.JWTParams{
   302  		Controller: coretesting.ControllerTag.Id(),
   303  		User:       user.String(),
   304  		Access: map[string]string{
   305  			coretesting.ControllerTag.String(): "superuser",
   306  			coretesting.ModelTag.String():      "write",
   307  		},
   308  	})
   309  	c.Assert(err, jc.ErrorIsNil)
   310  
   311  	delegator := &jwt.PermissionDelegator{token}
   312  	handler, _ := apiserver.TestingAPIHandlerWithToken(c, s.StatePool, s.State, token, delegator)
   313  	defer handler.Kill()
   314  	err = handler.HasPermission(permission.AdminAccess, coretesting.ModelTag)
   315  	var reqError *errors.AccessRequiredError
   316  	c.Assert(jujuerrors.As(err, &reqError), jc.IsTrue)
   317  	c.Assert(reqError, jc.DeepEquals, &errors.AccessRequiredError{
   318  		RequiredAccess: map[names.Tag]permission.Access{
   319  			coretesting.ModelTag: permission.AdminAccess,
   320  		},
   321  	})
   322  }
   323  
   324  func (s *serverSuite) TestAPIHandlerTeardownInitialModel(c *gc.C) {
   325  	s.checkAPIHandlerTeardown(c, s.State, s.State)
   326  }
   327  
   328  func (s *serverSuite) TestAPIHandlerTeardownOtherModel(c *gc.C) {
   329  	otherState := s.Factory.MakeModel(c, nil)
   330  	defer otherState.Close()
   331  	s.checkAPIHandlerTeardown(c, s.State, otherState)
   332  }
   333  
   334  func (s *serverSuite) TestAPIHandlerConnectedModel(c *gc.C) {
   335  	otherState := s.Factory.MakeModel(c, nil)
   336  	defer otherState.Close()
   337  	handler, _ := apiserver.TestingAPIHandler(c, s.StatePool, otherState)
   338  	defer handler.Kill()
   339  	c.Check(handler.ConnectedModel(), gc.Equals, otherState.ModelUUID())
   340  }
   341  
   342  func (s *serverSuite) TestClosesStateFromPool(c *gc.C) {
   343  	cfg := testserver.DefaultServerConfig(c, nil)
   344  	cfg.Controller = s.Controller
   345  	server := testserver.NewServerWithConfig(c, s.StatePool, cfg)
   346  	defer assertStop(c, server)
   347  
   348  	otherState := s.Factory.MakeModel(c, nil)
   349  	defer otherState.Close()
   350  
   351  	// Ensure the model's in the pool but not referenced.
   352  	st, err := s.StatePool.Get(otherState.ModelUUID())
   353  	c.Assert(err, jc.ErrorIsNil)
   354  	st.Release()
   355  
   356  	// Make a request for the model API to check it releases
   357  	// state back into the pool once the connection is closed.
   358  	addr := fmt.Sprintf("localhost:%d", server.Info.Ports()[0])
   359  	conn, err := dialWebsocket(c, addr, fmt.Sprintf("/model/%s/api", st.ModelUUID()))
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	conn.Close()
   362  
   363  	// Don't make an assertion about whether the remove call returns
   364  	// true - that's dependent on whether the server has reacted to
   365  	// the connection being closed yet, so it's racy.
   366  	_, err = s.StatePool.Remove(otherState.ModelUUID())
   367  	c.Assert(err, jc.ErrorIsNil)
   368  	assertStateBecomesClosed(c, st.State)
   369  }
   370  
   371  func assertStateBecomesClosed(c *gc.C, st *state.State) {
   372  	// This is gross but I can't see any other way to check for
   373  	// closedness outside the state package.
   374  	checkModel := func() {
   375  		attempt := utils.AttemptStrategy{
   376  			Total: coretesting.LongWait,
   377  			Delay: coretesting.ShortWait,
   378  		}
   379  		for a := attempt.Start(); a.Next(); {
   380  			// This will panic once the state is closed.
   381  			_, _ = st.Model()
   382  		}
   383  		// If we got here then st is still open.
   384  		st.Close()
   385  	}
   386  	c.Assert(checkModel, gc.PanicMatches, "Session already closed")
   387  }
   388  
   389  func (s *serverSuite) checkAPIHandlerTeardown(c *gc.C, srvSt, st *state.State) {
   390  	handler, resources := apiserver.TestingAPIHandler(c, s.StatePool, st)
   391  	resource := new(fakeResource)
   392  	resources.Register(resource)
   393  
   394  	c.Assert(resource.stopped, jc.IsFalse)
   395  	handler.Kill()
   396  	c.Assert(resource.stopped, jc.IsTrue)
   397  }
   398  
   399  type stopper interface {
   400  	Stop() error
   401  }
   402  
   403  func assertStop(c *gc.C, stopper stopper) {
   404  	c.Assert(stopper.Stop(), gc.IsNil)
   405  }
   406  
   407  type mockAuthenticator struct {
   408  }
   409  
   410  func (a *mockAuthenticator) Authenticate(req *http.Request) (authentication.AuthInfo, error) {
   411  	return authentication.AuthInfo{}, nil
   412  }
   413  
   414  func (a *mockAuthenticator) AuthenticateLoginRequest(
   415  	_ context.Context,
   416  	_,
   417  	_ string,
   418  	authParams authentication.AuthParams,
   419  ) (authentication.AuthInfo, error) {
   420  	return authentication.AuthInfo{
   421  		Entity: &mockEntity{tag: authParams.AuthTag},
   422  	}, nil
   423  }
   424  
   425  func (a *mockAuthenticator) CreateLocalLoginMacaroon(ctx context.Context, tag names.UserTag, version bakery.Version) (*macaroon.Macaroon, error) {
   426  	return nil, nil
   427  }
   428  
   429  type mockEntity struct {
   430  	tag names.Tag
   431  }
   432  
   433  func (e *mockEntity) Tag() names.Tag {
   434  	return e.tag
   435  }