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 }