github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/api/apiclient.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package api 5 6 import ( 7 "bufio" 8 "crypto/tls" 9 "crypto/x509" 10 "encoding/json" 11 "fmt" 12 "io" 13 "net/http" 14 "net/url" 15 "strings" 16 "sync/atomic" 17 "time" 18 19 "github.com/juju/errors" 20 "github.com/juju/loggo" 21 "github.com/juju/retry" 22 "github.com/juju/utils" 23 "github.com/juju/utils/clock" 24 "github.com/juju/utils/parallel" 25 "github.com/juju/version" 26 "golang.org/x/net/websocket" 27 "gopkg.in/juju/names.v2" 28 "gopkg.in/macaroon-bakery.v1/httpbakery" 29 "gopkg.in/macaroon.v1" 30 31 "github.com/juju/juju/api/base" 32 "github.com/juju/juju/apiserver/observer" 33 "github.com/juju/juju/apiserver/params" 34 "github.com/juju/juju/network" 35 "github.com/juju/juju/rpc" 36 "github.com/juju/juju/rpc/jsoncodec" 37 ) 38 39 var logger = loggo.GetLogger("juju.api") 40 41 // TODO(fwereade): we should be injecting a Clock; and injecting these values; 42 // across the board, instead of using these global variables. 43 var ( 44 // PingPeriod defines how often the internal connection health check 45 // will run. 46 PingPeriod = 1 * time.Minute 47 48 // PingTimeout defines how long a health check can take before we 49 // consider it to have failed. 50 PingTimeout = 30 * time.Second 51 ) 52 53 type rpcConnection interface { 54 Call(req rpc.Request, params, response interface{}) error 55 Close() error 56 } 57 58 // state is the internal implementation of the Connection interface. 59 type state struct { 60 client rpcConnection 61 conn *websocket.Conn 62 clock clock.Clock 63 64 // addr is the address used to connect to the API server. 65 addr string 66 67 // cookieURL is the URL that HTTP cookies for the API 68 // will be associated with (specifically macaroon auth cookies). 69 cookieURL *url.URL 70 71 // modelTag holds the model tag. 72 // It is empty if there is no model tag associated with the connection. 73 modelTag names.ModelTag 74 75 // controllerTag holds the controller's tag once we're connected. 76 controllerTag names.ControllerTag 77 78 // serverVersion holds the version of the API server that we are 79 // connected to. It is possible that this version is 0 if the 80 // server does not report this during login. 81 serverVersion version.Number 82 83 // hostPorts is the API server addresses returned from Login, 84 // which the client may cache and use for failover. 85 hostPorts [][]network.HostPort 86 87 // facadeVersions holds the versions of all facades as reported by 88 // Login 89 facadeVersions map[string][]int 90 91 // pingFacadeVersion is the version to use for the pinger. This is lazily 92 // set at initialization to avoid a race in our tests. See 93 // http://pad.lv/1614732 for more details regarding the race. 94 pingerFacadeVersion int 95 96 // authTag holds the authenticated entity's tag after login. 97 authTag names.Tag 98 99 // mpdelAccess holds the access level of the user to the connected model. 100 modelAccess string 101 102 // controllerAccess holds the access level of the user to the connected controller. 103 controllerAccess string 104 105 // broken is a channel that gets closed when the connection is 106 // broken. 107 broken chan struct{} 108 109 // closed is a channel that gets closed when State.Close is called. 110 closed chan struct{} 111 112 // loggedIn holds whether the client has successfully logged 113 // in. It's a int32 so that the atomic package can be used to 114 // access it safely. 115 loggedIn int32 116 117 // tag, password, macaroons and nonce hold the cached login 118 // credentials. These are only valid if loggedIn is 1. 119 tag string 120 password string 121 macaroons []macaroon.Slice 122 nonce string 123 124 // serverRootAddress holds the cached API server address and port used 125 // to login. 126 serverRootAddress string 127 128 // serverScheme is the URI scheme of the API Server 129 serverScheme string 130 131 // tlsConfig holds the TLS config appropriate for making SSL 132 // connections to the API endpoints. 133 tlsConfig *tls.Config 134 135 // certPool holds the cert pool that is used to authenticate the tls 136 // connections to the API. 137 certPool *x509.CertPool 138 139 // bakeryClient holds the client that will be used to 140 // authorize macaroon based login requests. 141 bakeryClient *httpbakery.Client 142 } 143 144 // RedirectError is returned from Open when the controller 145 // needs to inform the client that the model is hosted 146 // on a different set of API addresses. 147 type RedirectError struct { 148 // Servers holds the sets of addresses of the redirected 149 // servers. 150 Servers [][]network.HostPort 151 152 // CACert holds the certificate of the remote server. 153 CACert string 154 } 155 156 func (e *RedirectError) Error() string { 157 return fmt.Sprintf("redirection to alternative server required") 158 } 159 160 // Open establishes a connection to the API server using the Info 161 // given, returning a State instance which can be used to make API 162 // requests. 163 // 164 // If the model is hosted on a different server, Open 165 // will return an error with a *RedirectError cause 166 // holding the details of another server to connect to. 167 // 168 // See Connect for details of the connection mechanics. 169 func Open(info *Info, opts DialOpts) (Connection, error) { 170 return open(info, opts, clock.WallClock) 171 } 172 173 // open is the unexported version of open that also includes 174 // an explicit clock instance argument. 175 func open( 176 info *Info, 177 opts DialOpts, 178 clock clock.Clock, 179 ) (Connection, error) { 180 if err := info.Validate(); err != nil { 181 return nil, errors.Annotate(err, "validating info for opening an API connection") 182 } 183 if clock == nil { 184 return nil, errors.NotValidf("nil clock") 185 } 186 conn, tlsConfig, err := connectWebsocket(info, opts) 187 if err != nil { 188 return nil, errors.Trace(err) 189 } 190 191 client := rpc.NewConn(jsoncodec.NewWebsocket(conn), observer.None()) 192 client.Start() 193 194 bakeryClient := opts.BakeryClient 195 if bakeryClient == nil { 196 bakeryClient = httpbakery.NewClient() 197 } else { 198 // Make a copy of the bakery client and its HTTP client 199 c := *opts.BakeryClient 200 bakeryClient = &c 201 httpc := *bakeryClient.Client 202 bakeryClient.Client = &httpc 203 } 204 apiHost := conn.Config().Location.Host 205 // Technically when there's no CACert, we don't need this 206 // machinery, because we could just use http.DefaultTransport 207 // for everything, but it's easier just to leave it in place. 208 bakeryClient.Client.Transport = &hostSwitchingTransport{ 209 primaryHost: apiHost, 210 primary: utils.NewHttpTLSTransport(tlsConfig), 211 fallback: http.DefaultTransport, 212 } 213 214 st := &state{ 215 client: client, 216 conn: conn, 217 clock: clock, 218 addr: apiHost, 219 cookieURL: &url.URL{ 220 Scheme: "https", 221 Host: conn.Config().Location.Host, 222 Path: "/", 223 }, 224 pingerFacadeVersion: facadeVersions["Pinger"], 225 serverScheme: "https", 226 serverRootAddress: conn.Config().Location.Host, 227 // We populate the username and password before 228 // login because, when doing HTTP requests, we'll want 229 // to use the same username and password for authenticating 230 // those. If login fails, we discard the connection. 231 tag: tagToString(info.Tag), 232 password: info.Password, 233 macaroons: info.Macaroons, 234 nonce: info.Nonce, 235 tlsConfig: tlsConfig, 236 bakeryClient: bakeryClient, 237 modelTag: info.ModelTag, 238 } 239 if !info.SkipLogin { 240 if err := st.Login(info.Tag, info.Password, info.Nonce, info.Macaroons); err != nil { 241 conn.Close() 242 return nil, errors.Trace(err) 243 } 244 } 245 st.broken = make(chan struct{}) 246 st.closed = make(chan struct{}) 247 go st.heartbeatMonitor() 248 return st, nil 249 } 250 251 // hostSwitchingTransport provides an http.RoundTripper 252 // that chooses an actual RoundTripper to use 253 // depending on the destination host. 254 // 255 // This makes it possible to use a different set of root 256 // CAs for the API and all other hosts. 257 type hostSwitchingTransport struct { 258 primaryHost string 259 primary http.RoundTripper 260 fallback http.RoundTripper 261 } 262 263 // RoundTrip implements http.RoundTripper.RoundTrip. 264 func (t *hostSwitchingTransport) RoundTrip(req *http.Request) (*http.Response, error) { 265 if req.URL.Host == t.primaryHost { 266 return t.primary.RoundTrip(req) 267 } 268 return t.fallback.RoundTrip(req) 269 } 270 271 // connectWebsocket establishes a websocket connection to the RPC 272 // API websocket on the API server using Info. If multiple API addresses 273 // are provided in Info they will be tried concurrently - the first successful 274 // connection wins. 275 // 276 // It also returns the TLS configuration that it has derived from the Info. 277 func connectWebsocket(info *Info, opts DialOpts) (*websocket.Conn, *tls.Config, error) { 278 if len(info.Addrs) == 0 { 279 return nil, nil, errors.New("no API addresses to connect to") 280 } 281 tlsConfig := utils.SecureTLSConfig() 282 tlsConfig.InsecureSkipVerify = opts.InsecureSkipVerify 283 284 if info.CACert != "" && !tlsConfig.InsecureSkipVerify { 285 // We want to be specific here (rather than just using "anything". 286 // See commit 7fc118f015d8480dfad7831788e4b8c0432205e8 (PR 899). 287 tlsConfig.ServerName = "juju-apiserver" 288 certPool, err := CreateCertPool(info.CACert) 289 if err != nil { 290 return nil, nil, errors.Annotate(err, "cert pool creation failed") 291 } 292 tlsConfig.RootCAs = certPool 293 } 294 path, err := apiPath(info.ModelTag, "/api") 295 if err != nil { 296 return nil, nil, errors.Trace(err) 297 } 298 conn, err := dialWebSocket(info.Addrs, path, tlsConfig, opts) 299 if err != nil { 300 return nil, nil, errors.Trace(err) 301 } 302 logger.Infof("connection established to %q", conn.RemoteAddr()) 303 return conn, tlsConfig, nil 304 } 305 306 // dialWebSocket dials a websocket with one of the provided addresses, the 307 // specified URL path, TLS configuration, and dial options. Each of the 308 // specified addresses will be attempted concurrently, and the first 309 // successful connection will be returned. 310 func dialWebSocket(addrs []string, path string, tlsConfig *tls.Config, opts DialOpts) (*websocket.Conn, error) { 311 // Dial all addresses at reasonable intervals. 312 try := parallel.NewTry(0, nil) 313 defer try.Kill() 314 for _, addr := range addrs { 315 err := dialWebsocket(addr, path, opts, tlsConfig, try) 316 if err == parallel.ErrStopped { 317 break 318 } 319 if err != nil { 320 return nil, errors.Trace(err) 321 } 322 select { 323 case <-time.After(opts.DialAddressInterval): 324 case <-try.Dead(): 325 } 326 } 327 try.Close() 328 result, err := try.Result() 329 if err != nil { 330 return nil, errors.Trace(err) 331 } 332 return result.(*websocket.Conn), nil 333 } 334 335 // ConnectStream implements StreamConnector.ConnectStream. 336 func (st *state) ConnectStream(path string, attrs url.Values) (base.Stream, error) { 337 if !st.isLoggedIn() { 338 return nil, errors.New("cannot use ConnectStream without logging in") 339 } 340 // We use the standard "macaraq" macaroon authentication dance here. 341 // That is, we attach any macaroons we have to the initial request, 342 // and if that succeeds, all's good. If it fails with a DischargeRequired 343 // error, the response will contain a macaroon that, when discharged, 344 // may allow access, so we discharge it (using bakery.Client.HandleError) 345 // and try the request again. 346 conn, err := st.connectStream(path, attrs) 347 if err == nil { 348 return conn, err 349 } 350 if params.ErrCode(err) != params.CodeDischargeRequired { 351 return nil, errors.Trace(err) 352 } 353 if err := st.bakeryClient.HandleError(st.cookieURL, bakeryError(err)); err != nil { 354 return nil, errors.Trace(err) 355 } 356 // Try again with the discharged macaroon. 357 conn, err = st.connectStream(path, attrs) 358 if err != nil { 359 return nil, errors.Trace(err) 360 } 361 return conn, nil 362 } 363 364 // connectStream is the internal version of ConnectStream. It differs from 365 // ConnectStream only in that it will not retry the connection if it encounters 366 // discharge-required error. 367 func (st *state) connectStream(path string, attrs url.Values) (base.Stream, error) { 368 path, err := apiPath(st.modelTag, path) 369 if err != nil { 370 return nil, errors.Trace(err) 371 } 372 target := url.URL{ 373 Scheme: "wss", 374 Host: st.addr, 375 Path: path, 376 RawQuery: attrs.Encode(), 377 } 378 cfg, err := websocket.NewConfig(target.String(), "http://localhost/") 379 if st.tag != "" { 380 cfg.Header = utils.BasicAuthHeader(st.tag, st.password) 381 } 382 if st.nonce != "" { 383 cfg.Header.Set(params.MachineNonceHeader, st.nonce) 384 } 385 // Add any cookies because they will not be sent to websocket 386 // connections by default. 387 st.addCookiesToHeader(cfg.Header) 388 389 cfg.TlsConfig = st.tlsConfig 390 connection, err := websocketDialConfig(cfg) 391 if err != nil { 392 return nil, err 393 } 394 if err := readInitialStreamError(connection); err != nil { 395 return nil, errors.Trace(err) 396 } 397 return connection, nil 398 } 399 400 // readInitialStreamError reads the initial error response 401 // from a stream connection and returns it. 402 func readInitialStreamError(conn io.Reader) error { 403 // We can use bufio here because the websocket guarantees that a 404 // single read will not read more than a single frame; there is 405 // no guarantee that a single read might not read less than the 406 // whole frame though, so using a single Read call is not 407 // correct. By using ReadSlice rather than ReadBytes, we 408 // guarantee that the error can't be too big (>4096 bytes). 409 line, err := bufio.NewReader(conn).ReadSlice('\n') 410 if err != nil { 411 return errors.Annotate(err, "unable to read initial response") 412 } 413 var errResult params.ErrorResult 414 if err := json.Unmarshal(line, &errResult); err != nil { 415 return errors.Annotate(err, "unable to unmarshal initial response") 416 } 417 if errResult.Error != nil { 418 return errResult.Error 419 } 420 return nil 421 } 422 423 // addCookiesToHeader adds any cookies associated with the 424 // API host to the given header. This is necessary because 425 // otherwise cookies are not sent to websocket endpoints. 426 func (st *state) addCookiesToHeader(h http.Header) { 427 // net/http only allows adding cookies to a request, 428 // but when it sends a request to a non-http endpoint, 429 // it doesn't add the cookies, so make a request, starting 430 // with the given header, add the cookies to use, then 431 // throw away the request but keep the header. 432 req := &http.Request{ 433 Header: h, 434 } 435 cookies := st.bakeryClient.Client.Jar.Cookies(st.cookieURL) 436 for _, c := range cookies { 437 req.AddCookie(c) 438 } 439 } 440 441 // apiEndpoint returns a URL that refers to the given API slash-prefixed 442 // endpoint path and query parameters. 443 func (st *state) apiEndpoint(path, query string) (*url.URL, error) { 444 path, err := apiPath(st.modelTag, path) 445 if err != nil { 446 return nil, errors.Trace(err) 447 } 448 return &url.URL{ 449 Scheme: st.serverScheme, 450 Host: st.Addr(), 451 Path: path, 452 RawQuery: query, 453 }, nil 454 } 455 456 // apiPath returns the given API endpoint path relative 457 // to the given model tag. 458 func apiPath(modelTag names.ModelTag, path string) (string, error) { 459 if !strings.HasPrefix(path, "/") { 460 return "", errors.Errorf("cannot make API path from non-slash-prefixed path %q", path) 461 } 462 modelUUID := modelTag.Id() 463 if modelUUID == "" { 464 return path, nil 465 } 466 return "/model/" + modelUUID + path, nil 467 } 468 469 // tagToString returns the value of a tag's String method, or "" if the tag is nil. 470 func tagToString(tag names.Tag) string { 471 if tag == nil { 472 return "" 473 } 474 return tag.String() 475 } 476 477 func dialWebsocket(addr, path string, opts DialOpts, tlsConfig *tls.Config, try *parallel.Try) error { 478 // origin is required by the WebSocket API, used for "origin policy" 479 // in websockets. We pass localhost to satisfy the API; it is 480 // inconsequential to us. 481 const origin = "http://localhost/" 482 cfg, err := websocket.NewConfig("wss://"+addr+path, origin) 483 if err != nil { 484 return errors.Trace(err) 485 } 486 cfg.TlsConfig = tlsConfig 487 return try.Start(newWebsocketDialer(cfg, opts)) 488 } 489 490 // newWebsocketDialer returns a function that 491 // can be passed to utils/parallel.Try.Start. 492 var newWebsocketDialer = createWebsocketDialer 493 494 func createWebsocketDialer(cfg *websocket.Config, opts DialOpts) func(<-chan struct{}) (io.Closer, error) { 495 // TODO(katco): 2016-08-09: lp:1611427 496 openAttempt := utils.AttemptStrategy{ 497 Total: opts.Timeout, 498 Delay: opts.RetryDelay, 499 } 500 return func(stop <-chan struct{}) (io.Closer, error) { 501 for a := openAttempt.Start(); a.Next(); { 502 select { 503 case <-stop: 504 return nil, parallel.ErrStopped 505 default: 506 } 507 logger.Infof("dialing %q", cfg.Location) 508 conn, err := websocket.DialConfig(cfg) 509 if err == nil { 510 return conn, nil 511 } 512 if !a.HasNext() || isX509Error(err) { 513 // We won't reconnect when there's an X509 error 514 // because we're not going to succeed if we retry 515 // in that case. 516 logger.Infof("error dialing %q: %v", cfg.Location, err) 517 return nil, errors.Annotatef(err, "unable to connect to API") 518 } 519 } 520 panic("unreachable") 521 } 522 } 523 524 // isX509Error reports whether the given websocket error 525 // results from an X509 problem. 526 func isX509Error(err error) bool { 527 wsErr, ok := errors.Cause(err).(*websocket.DialError) 528 if !ok { 529 return false 530 } 531 switch wsErr.Err.(type) { 532 case x509.HostnameError, 533 x509.InsecureAlgorithmError, 534 x509.UnhandledCriticalExtension, 535 x509.UnknownAuthorityError, 536 x509.ConstraintViolationError, 537 x509.SystemRootsError: 538 return true 539 } 540 switch err { 541 case x509.ErrUnsupportedAlgorithm, 542 x509.IncorrectPasswordError: 543 return true 544 } 545 return false 546 } 547 548 func callWithTimeout(f func() error, timeout time.Duration) bool { 549 result := make(chan error, 1) 550 go func() { 551 // Note that result is buffered so that we don't leak this 552 // goroutine when a timeout happens. 553 result <- f() 554 }() 555 select { 556 case err := <-result: 557 if err != nil { 558 logger.Debugf("health ping failed: %v", err) 559 } 560 return err == nil 561 case <-time.After(timeout): 562 logger.Errorf("health ping timed out after %s", timeout) 563 return false 564 } 565 } 566 567 func (s *state) heartbeatMonitor() { 568 for { 569 if !callWithTimeout(s.Ping, PingTimeout) { 570 close(s.broken) 571 return 572 } 573 select { 574 case <-time.After(PingPeriod): 575 case <-s.closed: 576 } 577 } 578 } 579 580 func (s *state) Ping() error { 581 return s.APICall("Pinger", s.pingerFacadeVersion, "", "Ping", nil, nil) 582 } 583 584 type hasErrorCode interface { 585 ErrorCode() string 586 } 587 588 // APICall places a call to the remote machine. 589 // 590 // This fills out the rpc.Request on the given facade, version for a given 591 // object id, and the specific RPC method. It marshalls the Arguments, and will 592 // unmarshall the result into the response object that is supplied. 593 func (s *state) APICall(facade string, version int, id, method string, args, response interface{}) error { 594 retrySpec := retry.CallArgs{ 595 Func: func() error { 596 return s.client.Call(rpc.Request{ 597 Type: facade, 598 Version: version, 599 Id: id, 600 Action: method, 601 }, args, response) 602 }, 603 IsFatalError: func(err error) bool { 604 err = errors.Cause(err) 605 ec, ok := err.(hasErrorCode) 606 if !ok { 607 return true 608 } 609 return ec.ErrorCode() != params.CodeRetry 610 }, 611 Delay: 100 * time.Millisecond, 612 MaxDelay: 1500 * time.Millisecond, 613 MaxDuration: 10 * time.Second, 614 BackoffFunc: retry.DoubleDelay, 615 Clock: s.clock, 616 } 617 err := retry.Call(retrySpec) 618 return errors.Trace(err) 619 } 620 621 func (s *state) Close() error { 622 err := s.client.Close() 623 select { 624 case <-s.closed: 625 default: 626 close(s.closed) 627 } 628 <-s.broken 629 return err 630 } 631 632 // Broken returns a channel that's closed when the connection is broken. 633 func (s *state) Broken() <-chan struct{} { 634 return s.broken 635 } 636 637 // Addr returns the address used to connect to the API server. 638 func (s *state) Addr() string { 639 return s.addr 640 } 641 642 // ModelTag implements base.APICaller.ModelTag. 643 func (s *state) ModelTag() (names.ModelTag, bool) { 644 return s.modelTag, s.modelTag.Id() != "" 645 } 646 647 // ControllerTag implements base.APICaller.ControllerTag. 648 func (s *state) ControllerTag() names.ControllerTag { 649 return s.controllerTag 650 } 651 652 // APIHostPorts returns addresses that may be used to connect 653 // to the API server, including the address used to connect. 654 // 655 // The addresses are scoped (public, cloud-internal, etc.), so 656 // the client may choose which addresses to attempt. For the 657 // Juju CLI, all addresses must be attempted, as the CLI may 658 // be invoked both within and outside the model (think 659 // private clouds). 660 func (s *state) APIHostPorts() [][]network.HostPort { 661 // NOTE: We're making a copy of s.hostPorts before returning it, 662 // for safety. 663 hostPorts := make([][]network.HostPort, len(s.hostPorts)) 664 for i, server := range s.hostPorts { 665 hostPorts[i] = append([]network.HostPort{}, server...) 666 } 667 return hostPorts 668 } 669 670 // AllFacadeVersions returns what versions we know about for all facades 671 func (s *state) AllFacadeVersions() map[string][]int { 672 facades := make(map[string][]int, len(s.facadeVersions)) 673 for name, versions := range s.facadeVersions { 674 facades[name] = append([]int{}, versions...) 675 } 676 return facades 677 } 678 679 // BestFacadeVersion compares the versions of facades that we know about, and 680 // the versions available from the server, and reports back what version is the 681 // 'best available' to use. 682 // TODO(jam) this is the eventual implementation of what version of a given 683 // Facade we will want to use. It needs to line up the versions that the server 684 // reports to us, with the versions that our client knows how to use. 685 func (s *state) BestFacadeVersion(facade string) int { 686 return bestVersion(facadeVersions[facade], s.facadeVersions[facade]) 687 } 688 689 // serverRoot returns the cached API server address and port used 690 // to login, prefixed with "<URI scheme>://" (usually https). 691 func (s *state) serverRoot() string { 692 return s.serverScheme + "://" + s.serverRootAddress 693 } 694 695 func (s *state) isLoggedIn() bool { 696 return atomic.LoadInt32(&s.loggedIn) == 1 697 } 698 699 func (s *state) setLoggedIn() { 700 atomic.StoreInt32(&s.loggedIn, 1) 701 }