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