// connectStream is the internal version of ConnectStream. It differs from // ConnectStream only in that it will not retry the connection if it encounters // discharge-required error. func (st *state) connectStream(path string, attrs url.Values) (base.Stream, error) { path, err := apiPath(st.modelTag, path) if err != nil { return nil, errors.Trace(err) } target := url.URL{ Scheme: "wss", Host: st.addr, Path: path, RawQuery: attrs.Encode(), } cfg, err := websocket.NewConfig(target.String(), "http://localhost/") if st.tag != "" { cfg.Header = utils.BasicAuthHeader(st.tag, st.password) } if st.nonce != "" { cfg.Header.Set(params.MachineNonceHeader, st.nonce) } // Add any cookies because they will not be sent to websocket // connections by default. st.addCookiesToHeader(cfg.Header) cfg.TlsConfig = st.tlsConfig connection, err := websocketDialConfig(cfg) if err != nil { return nil, err } if err := readInitialStreamError(connection); err != nil { return nil, errors.Trace(err) } return connection, nil }
func dialLogsinkAPI(apiInfo *api.Info) (*websocket.Conn, error) { // TODO(mjs) Most of this should be extracted to be shared for // connections to both /log (debuglog) and /logsink. header := utils.BasicAuthHeader(apiInfo.Tag.String(), apiInfo.Password) header.Set("X-Juju-Nonce", apiInfo.Nonce) conn, err := api.Connect(apiInfo, "/logsink", header, api.DialOpts{}) if err != nil { return nil, errors.Annotate(err, "failed to connect to logsink API") } // Read the initial error and translate to a real error. // Read up to the first new line character. We can't use bufio here as it // reads too much from the reader. line := make([]byte, 4096) n, err := conn.Read(line) if err != nil { return nil, errors.Annotate(err, "unable to read initial response") } line = line[0:n] var errResult params.ErrorResult err = json.Unmarshal(line, &errResult) if err != nil { return nil, errors.Annotate(err, "unable to unmarshal initial response") } if errResult.Error != nil { return nil, errors.Annotatef(err, "initial server error") } return conn, nil }
func (s *debugLogBaseSuite) openWebsocketCustomPath(c *gc.C, path string) *bufio.Reader { server := s.logURL(c, "wss", nil) server.Path = path header := utils.BasicAuthHeader(s.userTag.String(), s.password) conn := s.dialWebsocketFromURL(c, server.String(), header) s.AddCleanup(func(_ *gc.C) { conn.Close() }) return bufio.NewReader(conn) }
func (s *httpSuite) TestBasicAuthHeader(c *gc.C) { header := utils.BasicAuthHeader("eric", "sekrit") c.Assert(len(header), gc.Equals, 1) auth := header.Get("Authorization") fields := strings.Fields(auth) c.Assert(len(fields), gc.Equals, 2) basic, encoded := fields[0], fields[1] c.Assert(basic, gc.Equals, "Basic") decoded, err := base64.StdEncoding.DecodeString(encoded) c.Assert(err, gc.IsNil) c.Assert(string(decoded), gc.Equals, "eric:sekrit") }
func (s *debugLogBaseSuite) TestAgentLoginsRejected(c *gc.C) { m, password := s.Factory.MakeMachineReturningPassword(c, &factory.MachineParams{ Nonce: "foo-nonce", }) header := utils.BasicAuthHeader(m.Tag().String(), password) header.Add(params.MachineNonceHeader, "foo-nonce") conn := s.dialWebsocketInternal(c, nil, header) defer conn.Close() reader := bufio.NewReader(conn) assertJSONError(c, reader, "invalid entity name or password") s.assertWebsocketClosed(c, reader) }
func (s *apiclientSuite) TestConnectWithHeader(c *gc.C) { var seenCfg *websocket.Config fakeNewDialer := func(cfg *websocket.Config, _ api.DialOpts) func(<-chan struct{}) (io.Closer, error) { seenCfg = cfg return func(<-chan struct{}) (io.Closer, error) { return nil, errors.New("fake") } } s.PatchValue(api.NewWebsocketDialerPtr, fakeNewDialer) header := utils.BasicAuthHeader("foo", "bar") api.Connect(s.APIInfo(c), "", header, api.DialOpts{}) // Return values not important here c.Assert(seenCfg, gc.NotNil) c.Assert(seenCfg.Header, gc.DeepEquals, header) }
// connectStream is the internal version of ConnectStream. It differs from // ConnectStream only in that it will not retry the connection if it encounters // discharge-required error. func (st *state) connectStream(path string, attrs url.Values) (base.Stream, error) { if !strings.HasPrefix(path, "/") { return nil, errors.New(`path must start with "/"`) } if _, ok := st.ServerVersion(); ok { // If the server version is set, then we know the server is capable of // serving streams at the model path. We also fully expect // that the server has returned a valid model tag. modelTag, err := st.ModelTag() if err != nil { return nil, errors.Annotate(err, "cannot get model tag, perhaps connected to system not model") } path = apiPath(modelTag, path) } target := url.URL{ Scheme: "wss", Host: st.addr, Path: path, RawQuery: attrs.Encode(), } cfg, err := websocket.NewConfig(target.String(), "http://localhost/") if st.tag != "" { cfg.Header = utils.BasicAuthHeader(st.tag, st.password) } if st.nonce != "" { cfg.Header.Set(params.MachineNonceHeader, st.nonce) } // Add any cookies because they will not be sent to websocket // connections by default. st.addCookiesToHeader(cfg.Header) cfg.TlsConfig = st.tlsConfig connection, err := websocketDialConfig(cfg) if err != nil { return nil, err } if err := readInitialStreamError(connection); err != nil { return nil, errors.Trace(err) } return connection, nil }
// WatchDebugLog returns a ReadCloser that the caller can read the log // lines from. Only log lines that match the filtering specified in // the DebugLogParams are returned. It returns an error that satisfies // errors.IsNotImplemented when the API server does not support the // end-point. // // TODO(dimitern) We already have errors.IsNotImplemented - why do we // need to define a different error for this purpose here? func (c *Client) WatchDebugLog(args DebugLogParams) (io.ReadCloser, error) { // The websocket connection just hangs if the server doesn't have the log // end point. So do a version check, as version was added at the same time // as the remote end point. _, err := c.AgentVersion() if err != nil { return nil, errors.NotSupportedf("WatchDebugLog") } // Prepare URL. attrs := url.Values{} if args.Replay { attrs.Set("replay", fmt.Sprint(args.Replay)) } if args.Limit > 0 { attrs.Set("maxLines", fmt.Sprint(args.Limit)) } if args.Backlog > 0 { attrs.Set("backlog", fmt.Sprint(args.Backlog)) } if args.Level != loggo.UNSPECIFIED { attrs.Set("level", fmt.Sprint(args.Level)) } attrs["includeEntity"] = args.IncludeEntity attrs["includeModule"] = args.IncludeModule attrs["excludeEntity"] = args.ExcludeEntity attrs["excludeModule"] = args.ExcludeModule path := "/log" if _, ok := c.st.ServerVersion(); ok { // If the server version is set, then we know the server is capable of // serving debug log at the environment path. We also fully expect // that the server has returned a valid environment tag. envTag, err := c.st.EnvironTag() if err != nil { return nil, errors.Annotate(err, "very unexpected") } path = fmt.Sprintf("/environment/%s/log", envTag.Id()) } target := url.URL{ Scheme: "wss", Host: c.st.addr, Path: path, RawQuery: attrs.Encode(), } cfg, err := websocket.NewConfig(target.String(), "http://localhost/") cfg.Header = utils.BasicAuthHeader(c.st.tag, c.st.password) cfg.TlsConfig = &tls.Config{RootCAs: c.st.certPool, ServerName: "juju-apiserver"} connection, err := websocketDialConfig(cfg) if err != nil { return nil, err } // Read the initial error and translate to a real error. // Read up to the first new line character. We can't use bufio here as it // reads too much from the reader. line := make([]byte, 4096) n, err := connection.Read(line) if err != nil { return nil, errors.Annotate(err, "unable to read initial response") } line = line[0:n] logger.Debugf("initial line: %q", line) var errResult params.ErrorResult err = json.Unmarshal(line, &errResult) if err != nil { return nil, errors.Annotate(err, "unable to unmarshal initial response") } if errResult.Error != nil { return nil, errResult.Error } return connection, nil }
func (s *logsinkSuite) TestRejectsIncorrectNonce(c *gc.C) { header := utils.BasicAuthHeader(s.machineTag.String(), s.password) header.Add(params.MachineNonceHeader, "wrong") s.checkAuthFails(c, header, "machine 0 not provisioned") }
func (s *logsinkSuite) TestRejectsBadPassword(c *gc.C) { header := utils.BasicAuthHeader(s.machineTag.String(), "wrong") header.Add(params.MachineNonceHeader, s.nonce) s.checkAuthFailsWithEntityError(c, header) }
func (s *logsinkSuite) TestRejectsUserLogins(c *gc.C) { user := s.Factory.MakeUser(c, &factory.UserParams{Password: "******"}) header := utils.BasicAuthHeader(user.Tag().String(), "sekrit") s.checkAuthFailsWithEntityError(c, header) }
func (s *logsinkSuite) makeAuthHeader() http.Header { header := utils.BasicAuthHeader(s.machineTag.String(), s.password) header.Add(params.MachineNonceHeader, s.nonce) return header }
func (s *debugLogBaseSuite) dialWebsocket(c *gc.C, queryParams url.Values) *websocket.Conn { header := utils.BasicAuthHeader(s.userTag.String(), s.password) return s.dialWebsocketInternal(c, queryParams, header) }
func (s *debugLogSuite) dialWebsocket(c *gc.C, queryParams url.Values) (*websocket.Conn, error) { header := utils.BasicAuthHeader(s.userTag, s.password) return s.dialWebsocketInternal(c, queryParams, header) }