github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  	"crypto/tls"
     8  	"crypto/x509"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"net/http"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/loggo"
    16  	"github.com/juju/names"
    17  	"github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/utils"
    20  	"golang.org/x/net/websocket"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/macaroon-bakery.v1/bakery"
    23  	"gopkg.in/macaroon-bakery.v1/bakery/checkers"
    24  	"gopkg.in/macaroon-bakery.v1/bakerytest"
    25  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    26  
    27  	"github.com/juju/juju/api"
    28  	apimachiner "github.com/juju/juju/api/machiner"
    29  	"github.com/juju/juju/apiserver"
    30  	"github.com/juju/juju/apiserver/params"
    31  	"github.com/juju/juju/cert"
    32  	"github.com/juju/juju/environs/config"
    33  	jujutesting "github.com/juju/juju/juju/testing"
    34  	"github.com/juju/juju/mongo/mongotest"
    35  	"github.com/juju/juju/network"
    36  	"github.com/juju/juju/rpc"
    37  	"github.com/juju/juju/state"
    38  	"github.com/juju/juju/state/presence"
    39  	coretesting "github.com/juju/juju/testing"
    40  	"github.com/juju/juju/testing/factory"
    41  )
    42  
    43  var fastDialOpts = api.DialOpts{}
    44  
    45  type serverSuite struct {
    46  	jujutesting.JujuConnSuite
    47  }
    48  
    49  var _ = gc.Suite(&serverSuite{})
    50  
    51  func (s *serverSuite) TestStop(c *gc.C) {
    52  	// Start our own instance of the server so we have
    53  	// a handle on it to stop it.
    54  	srv := newServer(c, s.State)
    55  	defer srv.Stop()
    56  
    57  	machine, password := s.Factory.MakeMachineReturningPassword(
    58  		c, &factory.MachineParams{Nonce: "fake_nonce"})
    59  
    60  	// A net.TCPAddr cannot be directly stringified into a valid hostname.
    61  	address := fmt.Sprintf("localhost:%d", srv.Addr().Port)
    62  
    63  	// Note we can't use openAs because we're not connecting to
    64  	apiInfo := &api.Info{
    65  		Tag:      machine.Tag(),
    66  		Password: password,
    67  		Nonce:    "fake_nonce",
    68  		Addrs:    []string{address},
    69  		CACert:   coretesting.CACert,
    70  		ModelTag: s.State.ModelTag(),
    71  	}
    72  	st, err := api.Open(apiInfo, fastDialOpts)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	defer st.Close()
    75  
    76  	_, err = apimachiner.NewState(st).Machine(machine.MachineTag())
    77  	c.Assert(err, jc.ErrorIsNil)
    78  
    79  	err = srv.Stop()
    80  	c.Assert(err, jc.ErrorIsNil)
    81  
    82  	_, err = apimachiner.NewState(st).Machine(machine.MachineTag())
    83  	err = errors.Cause(err)
    84  	// The client has not necessarily seen the server shutdown yet,
    85  	// so there are two possible errors.
    86  	if err != rpc.ErrShutdown && err != io.ErrUnexpectedEOF {
    87  		c.Fatalf("unexpected error from request: %#v, expected rpc.ErrShutdown or io.ErrUnexpectedEOF", err)
    88  	}
    89  
    90  	// Check it can be stopped twice.
    91  	err = srv.Stop()
    92  	c.Assert(err, jc.ErrorIsNil)
    93  }
    94  
    95  func (s *serverSuite) TestAPIServerCanListenOnBothIPv4AndIPv6(c *gc.C) {
    96  	err := s.State.SetAPIHostPorts(nil)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  
    99  	// Start our own instance of the server listening on
   100  	// both IPv4 and IPv6 localhost addresses and an ephemeral port.
   101  	srv := newServer(c, s.State)
   102  	defer srv.Stop()
   103  
   104  	port := srv.Addr().Port
   105  	portString := fmt.Sprintf("%d", port)
   106  
   107  	machine, password := s.Factory.MakeMachineReturningPassword(
   108  		c, &factory.MachineParams{Nonce: "fake_nonce"})
   109  
   110  	// Now connect twice - using IPv4 and IPv6 endpoints.
   111  	apiInfo := &api.Info{
   112  		Tag:      machine.Tag(),
   113  		Password: password,
   114  		Nonce:    "fake_nonce",
   115  		Addrs:    []string{net.JoinHostPort("127.0.0.1", portString)},
   116  		CACert:   coretesting.CACert,
   117  		ModelTag: s.State.ModelTag(),
   118  	}
   119  	ipv4State, err := api.Open(apiInfo, fastDialOpts)
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	defer ipv4State.Close()
   122  	c.Assert(ipv4State.Addr(), gc.Equals, net.JoinHostPort("127.0.0.1", portString))
   123  	c.Assert(ipv4State.APIHostPorts(), jc.DeepEquals, [][]network.HostPort{
   124  		network.NewHostPorts(port, "127.0.0.1"),
   125  	})
   126  
   127  	_, err = apimachiner.NewState(ipv4State).Machine(machine.MachineTag())
   128  	c.Assert(err, jc.ErrorIsNil)
   129  
   130  	apiInfo.Addrs = []string{net.JoinHostPort("::1", portString)}
   131  	ipv6State, err := api.Open(apiInfo, fastDialOpts)
   132  	c.Assert(err, jc.ErrorIsNil)
   133  	defer ipv6State.Close()
   134  	c.Assert(ipv6State.Addr(), gc.Equals, net.JoinHostPort("::1", portString))
   135  	c.Assert(ipv6State.APIHostPorts(), jc.DeepEquals, [][]network.HostPort{
   136  		network.NewHostPorts(port, "::1"),
   137  	})
   138  
   139  	_, err = apimachiner.NewState(ipv6State).Machine(machine.MachineTag())
   140  	c.Assert(err, jc.ErrorIsNil)
   141  }
   142  
   143  func (s *serverSuite) TestOpenAsMachineErrors(c *gc.C) {
   144  	assertNotProvisioned := func(err error) {
   145  		c.Assert(err, gc.NotNil)
   146  		c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned)
   147  		c.Assert(err, gc.ErrorMatches, `machine \d+ not provisioned \(not provisioned\)`)
   148  	}
   149  
   150  	machine, password := s.Factory.MakeMachineReturningPassword(
   151  		c, &factory.MachineParams{Nonce: "fake_nonce"})
   152  
   153  	// This does almost exactly the same as OpenAPIAsMachine but checks
   154  	// for failures instead.
   155  	info := s.APIInfo(c)
   156  	info.Tag = machine.Tag()
   157  	info.Password = password
   158  	info.Nonce = "invalid-nonce"
   159  	st, err := api.Open(info, fastDialOpts)
   160  	assertNotProvisioned(err)
   161  	c.Assert(st, gc.IsNil)
   162  
   163  	// Try with empty nonce as well.
   164  	info.Nonce = ""
   165  	st, err = api.Open(info, fastDialOpts)
   166  	assertNotProvisioned(err)
   167  	c.Assert(st, gc.IsNil)
   168  
   169  	// Finally, with the correct one succeeds.
   170  	info.Nonce = "fake_nonce"
   171  	st, err = api.Open(info, fastDialOpts)
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Assert(st, gc.NotNil)
   174  	st.Close()
   175  
   176  	// Now add another machine, intentionally unprovisioned.
   177  	stm1, err := s.State.AddMachine("quantal", state.JobHostUnits)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	err = stm1.SetPassword(password)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  
   182  	// Try connecting, it will fail.
   183  	info.Tag = stm1.Tag()
   184  	info.Nonce = ""
   185  	st, err = api.Open(info, fastDialOpts)
   186  	assertNotProvisioned(err)
   187  	c.Assert(st, gc.IsNil)
   188  }
   189  
   190  func (s *serverSuite) TestNewServerDoesNotAccessState(c *gc.C) {
   191  	mongoInfo := s.MongoInfo(c)
   192  
   193  	proxy := testing.NewTCPProxy(c, mongoInfo.Addrs[0])
   194  	mongoInfo.Addrs = []string{proxy.Addr()}
   195  
   196  	st, err := state.Open(s.State.ModelTag(), mongoInfo, mongotest.DialOpts(), nil)
   197  	c.Assert(err, gc.IsNil)
   198  	defer st.Close()
   199  
   200  	// Now close the proxy so that any attempts to use the
   201  	// controller will fail.
   202  	proxy.Close()
   203  
   204  	// Creating the server should succeed because it doesn't
   205  	// access the state (note that newServer does not log in,
   206  	// which *would* access the state).
   207  	srv := newServer(c, st)
   208  	srv.Stop()
   209  }
   210  
   211  func (s *serverSuite) TestMachineLoginStartsPinger(c *gc.C) {
   212  	// This is the same steps as OpenAPIAsNewMachine but we need to assert
   213  	// the agent is not alive before we actually open the API.
   214  	// Create a new machine to verify "agent alive" behavior.
   215  	machine, password := s.Factory.MakeMachineReturningPassword(
   216  		c, &factory.MachineParams{Nonce: "fake_nonce"})
   217  
   218  	// Not alive yet.
   219  	s.assertAlive(c, machine, false)
   220  
   221  	// Login as the machine agent of the created machine.
   222  	st := s.OpenAPIAsMachine(c, machine.Tag(), password, "fake_nonce")
   223  	defer func() {
   224  		err := st.Close()
   225  		c.Check(err, jc.ErrorIsNil)
   226  	}()
   227  
   228  	// Make sure the pinger has started.
   229  	s.assertAlive(c, machine, true)
   230  }
   231  
   232  func (s *serverSuite) TestUnitLoginStartsPinger(c *gc.C) {
   233  	// Create a new service and unit to verify "agent alive" behavior.
   234  	unit, password := s.Factory.MakeUnitReturningPassword(c, nil)
   235  
   236  	// Not alive yet.
   237  	s.assertAlive(c, unit, false)
   238  
   239  	// Login as the unit agent of the created unit.
   240  	st := s.OpenAPIAs(c, unit.Tag(), password)
   241  	defer func() {
   242  		err := st.Close()
   243  		c.Check(err, jc.ErrorIsNil)
   244  	}()
   245  
   246  	// Make sure the pinger has started.
   247  	s.assertAlive(c, unit, true)
   248  }
   249  
   250  func (s *serverSuite) assertAlive(c *gc.C, entity presence.Agent, expectAlive bool) {
   251  	s.State.StartSync()
   252  	alive, err := entity.AgentPresence()
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	c.Assert(alive, gc.Equals, expectAlive)
   255  }
   256  
   257  func dialWebsocket(c *gc.C, addr, path string, tlsVersion uint16) (*websocket.Conn, error) {
   258  	origin := "http://localhost/"
   259  	url := fmt.Sprintf("wss://%s%s", addr, path)
   260  	config, err := websocket.NewConfig(url, origin)
   261  	c.Assert(err, jc.ErrorIsNil)
   262  	pool := x509.NewCertPool()
   263  	xcert, err := cert.ParseCert(coretesting.CACert)
   264  	c.Assert(err, jc.ErrorIsNil)
   265  	pool.AddCert(xcert)
   266  	config.TlsConfig = utils.SecureTLSConfig()
   267  	if tlsVersion > 0 {
   268  		// This is for testing only. Please don't muck with the maxtlsversion in
   269  		// production.
   270  		config.TlsConfig.MaxVersion = tlsVersion
   271  	}
   272  	config.TlsConfig.RootCAs = pool
   273  	return websocket.DialConfig(config)
   274  }
   275  
   276  func (s *serverSuite) TestMinTLSVersion(c *gc.C) {
   277  	loggo.GetLogger("juju.apiserver").SetLogLevel(loggo.TRACE)
   278  	srv := newServer(c, s.State)
   279  	defer srv.Stop()
   280  
   281  	// We have to use 'localhost' because that is what the TLS cert says.
   282  	addr := fmt.Sprintf("localhost:%d", srv.Addr().Port)
   283  
   284  	// Specify an unsupported TLS version
   285  	conn, err := dialWebsocket(c, addr, "/", tls.VersionSSL30)
   286  	c.Assert(err, gc.ErrorMatches, ".*protocol version not supported")
   287  	c.Assert(conn, gc.IsNil)
   288  }
   289  
   290  func (s *serverSuite) TestNonCompatiblePathsAre404(c *gc.C) {
   291  	// we expose the API at '/' for compatibility, and at '/ModelUUID/api'
   292  	// for the correct location, but other Paths should fail.
   293  	loggo.GetLogger("juju.apiserver").SetLogLevel(loggo.TRACE)
   294  	srv := newServer(c, s.State)
   295  	defer srv.Stop()
   296  
   297  	// We have to use 'localhost' because that is what the TLS cert says.
   298  	addr := fmt.Sprintf("localhost:%d", srv.Addr().Port)
   299  	// '/' should be fine
   300  	conn, err := dialWebsocket(c, addr, "/", 0)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	conn.Close()
   303  	// '/model/MODELUUID/api' should be fine
   304  	conn, err = dialWebsocket(c, addr, "/model/dead-beef-123456/api", 0)
   305  	c.Assert(err, jc.ErrorIsNil)
   306  	conn.Close()
   307  
   308  	// '/randompath' is not ok
   309  	conn, err = dialWebsocket(c, addr, "/randompath", 0)
   310  	// Unfortunately go.net/websocket just returns Bad Status, it doesn't
   311  	// give us any information (whether this was a 404 Not Found, Internal
   312  	// Server Error, 200 OK, etc.)
   313  	c.Assert(err, gc.ErrorMatches, `websocket.Dial wss://localhost:\d+/randompath: bad status`)
   314  	c.Assert(conn, gc.IsNil)
   315  }
   316  
   317  func (s *serverSuite) TestNoBakeryWhenNoIdentityURL(c *gc.C) {
   318  	srv := newServer(c, s.State)
   319  	defer srv.Stop()
   320  	// By default, when there is no identity location, no
   321  	// bakery service or macaroon is created.
   322  	_, err := apiserver.ServerMacaroon(srv)
   323  	c.Assert(err, gc.ErrorMatches, "macaroon authentication is not configured")
   324  	_, err = apiserver.ServerBakeryService(srv)
   325  	c.Assert(err, gc.ErrorMatches, "macaroon authentication is not configured")
   326  }
   327  
   328  type macaroonServerSuite struct {
   329  	jujutesting.JujuConnSuite
   330  	discharger *bakerytest.Discharger
   331  }
   332  
   333  var _ = gc.Suite(&macaroonServerSuite{})
   334  
   335  func (s *macaroonServerSuite) SetUpTest(c *gc.C) {
   336  	s.discharger = bakerytest.NewDischarger(nil, noCheck)
   337  	s.ConfigAttrs = map[string]interface{}{
   338  		config.IdentityURL: s.discharger.Location(),
   339  	}
   340  	s.JujuConnSuite.SetUpTest(c)
   341  }
   342  
   343  func (s *macaroonServerSuite) TearDownTest(c *gc.C) {
   344  	s.discharger.Close()
   345  	s.JujuConnSuite.TearDownTest(c)
   346  }
   347  
   348  func (s *macaroonServerSuite) TestServerBakery(c *gc.C) {
   349  	srv := newServer(c, s.State)
   350  	defer srv.Stop()
   351  	m, err := apiserver.ServerMacaroon(srv)
   352  	c.Assert(err, gc.IsNil)
   353  	bsvc, err := apiserver.ServerBakeryService(srv)
   354  	c.Assert(err, gc.IsNil)
   355  
   356  	// Check that we can add a third party caveat addressed to the
   357  	// discharger, which indirectly ensures that the discharger's public
   358  	// key has been added to the bakery service's locator.
   359  	m = m.Clone()
   360  	err = bsvc.AddCaveat(m, checkers.Caveat{
   361  		Location:  s.discharger.Location(),
   362  		Condition: "true",
   363  	})
   364  	c.Assert(err, jc.ErrorIsNil)
   365  
   366  	// Check that we can discharge the macaroon and check it with
   367  	// the service.
   368  	client := httpbakery.NewClient()
   369  	ms, err := client.DischargeAll(m)
   370  	c.Assert(err, jc.ErrorIsNil)
   371  
   372  	err = bsvc.(*bakery.Service).Check(ms, checkers.New())
   373  	c.Assert(err, gc.IsNil)
   374  }
   375  
   376  type macaroonServerWrongPublicKeySuite struct {
   377  	jujutesting.JujuConnSuite
   378  	discharger *bakerytest.Discharger
   379  }
   380  
   381  var _ = gc.Suite(&macaroonServerWrongPublicKeySuite{})
   382  
   383  func (s *macaroonServerWrongPublicKeySuite) SetUpTest(c *gc.C) {
   384  	s.discharger = bakerytest.NewDischarger(nil, noCheck)
   385  	wrongKey, err := bakery.GenerateKey()
   386  	c.Assert(err, gc.IsNil)
   387  	s.ConfigAttrs = map[string]interface{}{
   388  		config.IdentityURL:       s.discharger.Location(),
   389  		config.IdentityPublicKey: wrongKey.Public.String(),
   390  	}
   391  	s.JujuConnSuite.SetUpTest(c)
   392  }
   393  
   394  func (s *macaroonServerWrongPublicKeySuite) TearDownTest(c *gc.C) {
   395  	s.discharger.Close()
   396  	s.JujuConnSuite.TearDownTest(c)
   397  }
   398  
   399  func (s *macaroonServerWrongPublicKeySuite) TestDischargeFailsWithWrongPublicKey(c *gc.C) {
   400  	srv := newServer(c, s.State)
   401  	defer srv.Stop()
   402  	m, err := apiserver.ServerMacaroon(srv)
   403  	c.Assert(err, gc.IsNil)
   404  	m = m.Clone()
   405  	bsvc, err := apiserver.ServerBakeryService(srv)
   406  	c.Assert(err, gc.IsNil)
   407  	err = bsvc.AddCaveat(m, checkers.Caveat{
   408  		Location:  s.discharger.Location(),
   409  		Condition: "true",
   410  	})
   411  	c.Assert(err, gc.IsNil)
   412  	client := httpbakery.NewClient()
   413  
   414  	_, err = client.DischargeAll(m)
   415  	c.Assert(err, gc.ErrorMatches, `cannot get discharge from ".*": third party refused discharge: cannot discharge: discharger cannot decode caveat id: public key mismatch`)
   416  }
   417  
   418  func noCheck(req *http.Request, cond, arg string) ([]checkers.Caveat, error) {
   419  	return nil, nil
   420  }
   421  
   422  type fakeResource struct {
   423  	stopped bool
   424  }
   425  
   426  func (r *fakeResource) Stop() error {
   427  	r.stopped = true
   428  	return nil
   429  }
   430  
   431  func (s *serverSuite) TestApiHandlerTeardownInitialEnviron(c *gc.C) {
   432  	s.checkApiHandlerTeardown(c, s.State, s.State)
   433  }
   434  
   435  func (s *serverSuite) TestApiHandlerTeardownOtherEnviron(c *gc.C) {
   436  	otherState := s.Factory.MakeModel(c, nil)
   437  	defer otherState.Close()
   438  	s.checkApiHandlerTeardown(c, s.State, otherState)
   439  }
   440  
   441  func (s *serverSuite) checkApiHandlerTeardown(c *gc.C, srvSt, st *state.State) {
   442  	handler, resources := apiserver.TestingApiHandler(c, srvSt, st)
   443  	resource := new(fakeResource)
   444  	resources.Register(resource)
   445  
   446  	c.Assert(resource.stopped, jc.IsFalse)
   447  	handler.Kill()
   448  	c.Assert(resource.stopped, jc.IsTrue)
   449  }
   450  
   451  // newServer returns a new running API server.
   452  func newServer(c *gc.C, st *state.State) *apiserver.Server {
   453  	listener, err := net.Listen("tcp", ":0")
   454  	c.Assert(err, jc.ErrorIsNil)
   455  	srv, err := apiserver.NewServer(st, listener, apiserver.ServerConfig{
   456  		Cert:   []byte(coretesting.ServerCert),
   457  		Key:    []byte(coretesting.ServerKey),
   458  		Tag:    names.NewMachineTag("0"),
   459  		LogDir: c.MkDir(),
   460  	})
   461  	c.Assert(err, jc.ErrorIsNil)
   462  	return srv
   463  }