func (certSuite) TestParseCertificate(c *gc.C) { xcert, err := cert.ParseCert(caCertPEM) c.Assert(err, jc.ErrorIsNil) c.Assert(xcert.Subject.CommonName, gc.Equals, "juju testing") xcert, err = cert.ParseCert(caKeyPEM) c.Check(xcert, gc.IsNil) c.Assert(err, gc.ErrorMatches, "no certificates found") xcert, err = cert.ParseCert("hello") c.Check(xcert, gc.IsNil) c.Assert(err, gc.ErrorMatches, "no certificates found") }
func (certSuite) TestWithNonUTCExpiry(c *gc.C) { expiry, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", "2012-11-28 15:53:57 +0100 CET") c.Assert(err, jc.ErrorIsNil) certPEM, keyPEM, err := cert.NewCA("foo", expiry) xcert, err := cert.ParseCert(certPEM) c.Assert(err, jc.ErrorIsNil) checkNotAfter(c, xcert, expiry) var noHostnames []string certPEM, _, err = cert.NewServer(certPEM, keyPEM, expiry, noHostnames) xcert, err = cert.ParseCert(certPEM) c.Assert(err, jc.ErrorIsNil) checkNotAfter(c, xcert, expiry) }
func mustParseCert(pemData string) *x509.Certificate { cert, err := cert.ParseCert(pemData) if err != nil { panic(err) } return cert }
// Validate ensures that config is a valid configuration. func Validate(c Config) error { if v, ok := c[IdentityURL].(string); ok { u, err := url.Parse(v) if err != nil { return errors.Annotate(err, "invalid identity URL") } if u.Scheme != "https" { return errors.Errorf("URL needs to be https") } } if v, ok := c[IdentityPublicKey].(string); ok { var key bakery.PublicKey if err := key.UnmarshalText([]byte(v)); err != nil { return errors.Annotate(err, "invalid identity public key") } } caCert, caCertOK := c.CACert() if !caCertOK { return errors.Errorf("missing CA certificate") } if _, err := cert.ParseCert(caCert); err != nil { return errors.Annotate(err, "bad CA certificate in configuration") } if uuid, ok := c[ControllerUUIDKey].(string); ok && !utils.IsValidUUIDString(uuid) { return errors.Errorf("controller-uuid: expected UUID, got string(%q)", uuid) } return nil }
func (s *RsyslogSuite) TestRsyslogCert(c *gc.C) { st, m := s.st, s.machine err := s.machine.SetProviderAddresses(network.NewAddress("example.com")) c.Assert(err, jc.ErrorIsNil) worker, err := rsyslog.NewRsyslogConfigWorker(st.Rsyslog(), rsyslog.RsyslogModeAccumulate, m.Tag(), "", []string{"0.1.2.3"}) c.Assert(err, jc.ErrorIsNil) defer func() { c.Assert(worker.Wait(), gc.IsNil) }() defer worker.Kill() waitForFile(c, filepath.Join(*rsyslog.LogDir, "rsyslog-cert.pem")) rsyslogCertPEM, err := ioutil.ReadFile(filepath.Join(*rsyslog.LogDir, "rsyslog-cert.pem")) c.Assert(err, jc.ErrorIsNil) cert, err := cert.ParseCert(string(rsyslogCertPEM)) c.Assert(err, jc.ErrorIsNil) c.Assert(cert.DNSNames, gc.DeepEquals, []string{"example.com", "*"}) subject := cert.Subject c.Assert(subject.CommonName, gc.Equals, "*") c.Assert(subject.Organization, gc.DeepEquals, []string{"juju"}) issuer := cert.Issuer c.Assert(issuer.CommonName, gc.Equals, "juju-generated CA for environment \"rsyslog\"") c.Assert(issuer.Organization, gc.DeepEquals, []string{"juju"}) }
func (s *CertUpdaterSuite) TestStartStop(c *gc.C) { var initialAddresses []string setter := func(info params.StateServingInfo, dying <-chan struct{}) error { // Only care about first time called. if len(initialAddresses) > 0 { return nil } srvCert, err := cert.ParseCert(info.Cert) c.Assert(err, jc.ErrorIsNil) initialAddresses = make([]string, len(srvCert.IPAddresses)) for i, ip := range srvCert.IPAddresses { initialAddresses[i] = ip.String() } return nil } changes := make(chan struct{}) certChangedChan := make(chan params.StateServingInfo) worker := certupdater.NewCertificateUpdater( &mockMachine{changes}, s, &mockConfigGetter{}, &mockAPIHostGetter{}, setter, certChangedChan, ) worker.Kill() c.Assert(worker.Wait(), gc.IsNil) // Initial cert addresses initialised to cloud local ones. c.Assert(initialAddresses, jc.DeepEquals, []string{"192.168.1.1"}) }
func (s *MachineSuite) TestCertificateUpdateWorkerUpdatesCertificate(c *gc.C) { // Set up the machine agent. m, _, _ := s.primeAgent(c, state.JobManageModel) a := s.newAgent(c, m) a.ReadConfig(names.NewMachineTag(m.Id()).String()) // Set up check that certificate has been updated. updated := make(chan struct{}) go func() { for { stateInfo, _ := a.CurrentConfig().StateServingInfo() srvCert, err := cert.ParseCert(stateInfo.Cert) if !c.Check(err, jc.ErrorIsNil) { break } sanIPs := make([]string, len(srvCert.IPAddresses)) for i, ip := range srvCert.IPAddresses { sanIPs[i] = ip.String() } if len(sanIPs) == 1 && sanIPs[0] == "0.1.2.3" { close(updated) break } time.Sleep(100 * time.Millisecond) } }() go func() { c.Check(a.Run(nil), jc.ErrorIsNil) }() defer func() { c.Check(a.Stop(), jc.ErrorIsNil) }() s.assertChannelActive(c, updated, "certificate to be updated") }
func (s *syslogSuite) createSyslogServer(c *gc.C, received chan rfc5424test.Message, done chan struct{}) string { server := rfc5424test.NewServer(rfc5424test.HandlerFunc(func(msg rfc5424test.Message) { select { case received <- msg: case <-done: } })) s.AddCleanup(func(*gc.C) { server.Close() }) s.AddCleanup(func(*gc.C) { close(done) }) serverCert, err := tls.X509KeyPair( []byte(coretesting.ServerCert), []byte(coretesting.ServerKey), ) c.Assert(err, jc.ErrorIsNil) caCert, err := cert.ParseCert(coretesting.CACert) c.Assert(err, jc.ErrorIsNil) clientCAs := x509.NewCertPool() clientCAs.AddCert(caCert) server.TLS = &tls.Config{ Certificates: []tls.Certificate{serverCert}, ClientCAs: clientCAs, } server.StartTLS() // We must use "localhost", as the certificate does not // have any IP SANs. port := server.Listener.Addr().(*net.TCPAddr).Port addr := net.JoinHostPort("localhost", fmt.Sprint(port)) return addr }
// verifyKeyPair verifies that the certificate and key parse correctly. // The key is optional - if it is provided, we also check that the key // matches the certificate. func verifyKeyPair(certb, key string) error { if key != "" { _, err := tls.X509KeyPair([]byte(certb), []byte(key)) return err } _, err := cert.ParseCert(certb) return err }
func dialWebsocket(c *gc.C, addr, path string) (*websocket.Conn, error) { origin := "http://localhost/" url := fmt.Sprintf("wss://%s%s", addr, path) config, err := websocket.NewConfig(url, origin) c.Assert(err, jc.ErrorIsNil) pool := x509.NewCertPool() xcert, err := cert.ParseCert(coretesting.CACert) c.Assert(err, jc.ErrorIsNil) pool.AddCert(xcert) config.TlsConfig = &tls.Config{RootCAs: pool} return websocket.DialConfig(config) }
// DialInfo returns information on how to dial // the state's mongo server with the given info // and dial options. func DialInfo(info Info, opts DialOpts) (*mgo.DialInfo, error) { if len(info.Addrs) == 0 { return nil, stderrors.New("no mongo addresses") } if len(info.CACert) == 0 { return nil, stderrors.New("missing CA certificate") } xcert, err := cert.ParseCert(info.CACert) if err != nil { return nil, fmt.Errorf("cannot parse CA certificate: %v", err) } pool := x509.NewCertPool() pool.AddCert(xcert) tlsConfig := utils.SecureTLSConfig() // TODO(natefinch): revisit this when are full-time on mongo 3. // We have to add non-ECDHE suites because mongo doesn't support ECDHE. moreSuites := []uint16{ tls.TLS_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_RSA_WITH_AES_256_GCM_SHA384, } tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, moreSuites...) tlsConfig.RootCAs = pool tlsConfig.ServerName = "juju-mongodb" dial := func(server *mgo.ServerAddr) (net.Conn, error) { addr := server.TCPAddr().String() c, err := net.DialTimeout("tcp", addr, opts.Timeout) if err != nil { logger.Warningf("mongodb connection failed, will retry: %v", err) return nil, err } cc := tls.Client(c, tlsConfig) if err := cc.Handshake(); err != nil { logger.Warningf("TLS handshake failed: %v", err) return nil, err } logger.Debugf("dialled mongodb server at %q", addr) return cc, nil } return &mgo.DialInfo{ Addrs: info.Addrs, Timeout: opts.Timeout, DialServer: dial, Direct: opts.Direct, }, nil }
// SetRsyslogCert sets the rsyslog CACert. func (api *RsyslogAPI) SetRsyslogCert(args params.SetRsyslogCertParams) (params.ErrorResult, error) { var result params.ErrorResult if !api.canModify { result.Error = common.ServerError(common.ErrBadCreds) return result, nil } if _, err := cert.ParseCert(string(args.CACert)); err != nil { result.Error = common.ServerError(err) return result, nil } attrs := map[string]interface{}{"rsyslog-ca-cert": string(args.CACert)} if err := api.st.UpdateEnvironConfig(attrs, nil, nil); err != nil { result.Error = common.ServerError(err) } return result, nil }
// updateRequired returns true and a list of merged addresses if any of the // new addresses are not yet contained in the server cert SAN list. func updateRequired(serverCert string, newAddrs []string) ([]string, bool, error) { x509Cert, err := cert.ParseCert(serverCert) if err != nil { return nil, false, errors.Annotate(err, "cannot parse existing TLS certificate") } existingAddr := set.NewStrings() for _, ip := range x509Cert.IPAddresses { existingAddr.Add(ip.String()) } logger.Debugf("existing cert addresses %v", existingAddr) logger.Debugf("new addresses %v", newAddrs) // Does newAddr contain any that are not already in existingAddr? newAddrSet := set.NewStrings(newAddrs...) update := newAddrSet.Difference(existingAddr).Size() > 0 newAddrSet = newAddrSet.Union(existingAddr) return newAddrSet.SortedValues(), update, nil }
func dialWebsocket(c *gc.C, addr, path string, tlsVersion uint16) (*websocket.Conn, error) { origin := "http://localhost/" url := fmt.Sprintf("wss://%s%s", addr, path) config, err := websocket.NewConfig(url, origin) c.Assert(err, jc.ErrorIsNil) pool := x509.NewCertPool() xcert, err := cert.ParseCert(coretesting.CACert) c.Assert(err, jc.ErrorIsNil) pool.AddCert(xcert) config.TlsConfig = utils.SecureTLSConfig() if tlsVersion > 0 { // This is for testing only. Please don't muck with the maxtlsversion in // production. config.TlsConfig.MaxVersion = tlsVersion } config.TlsConfig.RootCAs = pool return websocket.DialConfig(config) }
func (cfg RawConfig) tlsConfig() (*tls.Config, error) { clientCert, err := tls.X509KeyPair([]byte(cfg.ClientCert), []byte(cfg.ClientKey)) if err != nil { return nil, errors.Annotate(err, "parsing client key pair") } caCert, err := cert.ParseCert(cfg.CACert) if err != nil { return nil, errors.Annotate(err, "parsing CA certificate") } rootCAs := x509.NewCertPool() rootCAs.AddCert(caCert) return &tls.Config{ Certificates: []tls.Certificate{clientCert}, RootCAs: rootCAs, }, nil }
// CreateCertPool creates a new x509.CertPool and adds in the caCert passed // in. All certs from the cert directory (/etc/juju/cert.d on ubuntu) are // also added. func CreateCertPool(caCert string) (*x509.CertPool, error) { pool := x509.NewCertPool() if caCert != "" { xcert, err := cert.ParseCert(caCert) if err != nil { return nil, errors.Trace(err) } pool.AddCert(xcert) } count := processCertDir(pool) if count >= 0 { logger.Debugf("added %d certs to the pool from %s", count, certDir) } return pool, nil }
func addDownloadToolsCmds(ser string, certificate string, toolsList tools.List) ([]string, error) { var cmds []string var getDownloadFileCmd func(url string) string if series.IsWindowsNano(ser) { parsedCert, err := cert.ParseCert(certificate) if err != nil { return nil, err } caCert := base64.URLEncoding.EncodeToString(parsedCert.Raw) cmds = []string{fmt.Sprintf(`$cacert = "%s"`, caCert), `$cert_bytes = $cacert | %{ ,[System.Text.Encoding]::UTF8.GetBytes($_) }`, `$cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2(,$cert_bytes)`, `$store = Get-Item Cert:\LocalMachine\AuthRoot`, `$store.Open("ReadWrite")`, `$store.Add($cert)`, } getDownloadFileCmd = func(url string) string { return fmt.Sprintf(`Invoke-FastWebRequest -URI '%s' -OutFile "$binDir\tools.tar.gz"`, url) } } else { cmds = []string{ `$WebClient = New-Object System.Net.WebClient`, `[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}`, `[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12`, } getDownloadFileCmd = func(url string) string { return fmt.Sprintf(`$WebClient.DownloadFile('%s', "$binDir\tools.tar.gz");`, url) } } // Attempt all of the URLs, one after the other, until one succeeds. // If all of the URLs fail, we retry the whole lot. We retry in this // way, rather than retrying individually, to avoid one permanently // bad URL from holding up the download. downloadCmds := make([]string, len(toolsList)) for i, tools := range toolsList { downloadCmds[i] = fmt.Sprintf("{ %s }", getDownloadFileCmd(tools.URL)) } downloadCmd := fmt.Sprintf("ExecRetry { TryExecAll @(%s) }", strings.Join(downloadCmds, ", ")) cmds = append(cmds, downloadCmd) return cmds, nil }
// Connect establishes a websocket connection to the API server using // the Info, API path tail and (optional) request headers provided. If // multiple API addresses are provided in Info they will be tried // concurrently - the first successful connection wins. // // The path tail may be blank, in which case the default value will be // used. Otherwise, it must start with a "/". func Connect(info *Info, pathTail string, header http.Header, opts DialOpts) (*websocket.Conn, error) { if len(info.Addrs) == 0 { return nil, errors.New("no API addresses to connect to") } if pathTail != "" && !strings.HasPrefix(pathTail, "/") { return nil, errors.New(`path tail must start with "/"`) } pool := x509.NewCertPool() xcert, err := cert.ParseCert(info.CACert) if err != nil { return nil, errors.Annotate(err, "cert pool creation failed") } pool.AddCert(xcert) path := makeAPIPath(info.EnvironTag.Id(), pathTail) // Dial all addresses at reasonable intervals. try := parallel.NewTry(0, nil) defer try.Kill() for _, addr := range info.Addrs { err := dialWebsocket(addr, path, header, opts, pool, try) if err == parallel.ErrStopped { break } if err != nil { return nil, errors.Trace(err) } select { case <-time.After(opts.DialAddressInterval): case <-try.Dead(): } } try.Close() result, err := try.Result() if err != nil { return nil, errors.Trace(err) } conn := result.(*websocket.Conn) logger.Infof("connection established to %q", conn.RemoteAddr()) return conn, nil }
func (s *CertUpdaterSuite) TestAddressChange(c *gc.C) { var srvCert *x509.Certificate updated := make(chan struct{}) setter := func(info params.StateServingInfo, dying <-chan struct{}) error { s.stateServingInfo = info var err error srvCert, err = cert.ParseCert(info.Cert) c.Assert(err, jc.ErrorIsNil) sanIPs := make([]string, len(srvCert.IPAddresses)) for i, ip := range srvCert.IPAddresses { sanIPs[i] = ip.String() } sanIPsSet := set.NewStrings(sanIPs...) if sanIPsSet.Size() == 2 && sanIPsSet.Contains("0.1.2.3") && sanIPsSet.Contains("192.168.1.1") { close(updated) } return nil } changes := make(chan struct{}) certChangedChan := make(chan params.StateServingInfo) worker := certupdater.NewCertificateUpdater( &mockMachine{changes}, s, &mockConfigGetter{}, &mockAPIHostGetter{}, setter, certChangedChan, ) defer func() { c.Assert(worker.Wait(), gc.IsNil) }() defer worker.Kill() changes <- struct{}{} // Certificate should be updated with the address value. select { case <-updated: case <-time.After(coretesting.LongWait): c.Fatalf("timed out waiting for certificate to be updated") } // The server certificates must report "juju-apiserver" as a DNS // name for backwards-compatibility with API clients. They must // also report "juju-mongodb" because these certicates are also // used for serving MongoDB connections. c.Assert(srvCert.DNSNames, jc.SameContents, []string{"localhost", "juju-apiserver", "juju-mongodb", "anything"}) }
func (s *MachineSuite) TestCertificateDNSUpdated(c *gc.C) { // Disable the certificate work so it doesn't update the certificate. newUpdater := func(certupdater.AddressWatcher, certupdater.StateServingInfoGetter, certupdater.ControllerConfigGetter, certupdater.APIHostPortsGetter, certupdater.StateServingInfoSetter, ) worker.Worker { return worker.NewNoOpWorker() } s.PatchValue(&newCertificateUpdater, newUpdater) // Set up the machine agent. m, _, _ := s.primeAgent(c, state.JobManageModel) a := s.newAgent(c, m) // Set up check that certificate has been updated when the agent starts. updated := make(chan struct{}) expectedDnsNames := set.NewStrings("local", "juju-apiserver", "juju-mongodb") go func() { for { stateInfo, _ := a.CurrentConfig().StateServingInfo() srvCert, err := cert.ParseCert(stateInfo.Cert) c.Assert(err, jc.ErrorIsNil) certDnsNames := set.NewStrings(srvCert.DNSNames...) if !expectedDnsNames.Difference(certDnsNames).IsEmpty() { continue } pemContent, err := ioutil.ReadFile(filepath.Join(s.DataDir(), "server.pem")) c.Assert(err, jc.ErrorIsNil) if string(pemContent) == stateInfo.Cert+"\n"+stateInfo.PrivateKey { close(updated) break } time.Sleep(10 * time.Millisecond) } }() go func() { c.Check(a.Run(nil), jc.ErrorIsNil) }() defer func() { c.Check(a.Stop(), jc.ErrorIsNil) }() s.assertChannelActive(c, updated, "certificate to be updated") }
// DialInfo returns information on how to dial // the state's mongo server with the given info // and dial options. func DialInfo(info Info, opts DialOpts) (*mgo.DialInfo, error) { if len(info.Addrs) == 0 { return nil, stderrors.New("no mongo addresses") } if len(info.CACert) == 0 { return nil, stderrors.New("missing CA certificate") } xcert, err := cert.ParseCert(info.CACert) if err != nil { return nil, fmt.Errorf("cannot parse CA certificate: %v", err) } pool := x509.NewCertPool() pool.AddCert(xcert) tlsConfig := &tls.Config{ RootCAs: pool, ServerName: "juju-mongodb", } dial := func(addr net.Addr) (net.Conn, error) { c, err := net.Dial("tcp", addr.String()) if err != nil { logger.Debugf("connection failed, will retry: %v", err) return nil, err } cc := tls.Client(c, tlsConfig) if err := cc.Handshake(); err != nil { logger.Debugf("TLS handshake failed: %v", err) return nil, err } logger.Infof("dialled mongo successfully on address %q", addr) return cc, nil } return &mgo.DialInfo{ Addrs: info.Addrs, Timeout: opts.Timeout, Dial: dial, Direct: opts.Direct, }, nil }
// upgradeCertificateDNSNames ensure that the controller certificate // recorded in the agent config and also mongo server.pem contains the // DNSNames entires required by Juju/ func (a *MachineAgent) upgradeCertificateDNSNames() error { agentConfig := a.CurrentConfig() si, ok := agentConfig.StateServingInfo() if !ok || si.CAPrivateKey == "" { // No certificate information exists yet, nothing to do. return nil } // Parse the current certificate to get the current dns names. serverCert, err := cert.ParseCert(si.Cert) if err != nil { return err } update := false dnsNames := set.NewStrings(serverCert.DNSNames...) requiredDNSNames := []string{"local", "juju-apiserver", "juju-mongodb"} for _, dnsName := range requiredDNSNames { if dnsNames.Contains(dnsName) { continue } dnsNames.Add(dnsName) update = true } if !update { return nil } // Write a new certificate to the mongo pem and agent config files. si.Cert, si.PrivateKey, err = cert.NewDefaultServer(agentConfig.CACert(), si.CAPrivateKey, dnsNames.Values()) if err != nil { return err } if err := mongo.UpdateSSLKey(agentConfig.DataDir(), si.Cert, si.PrivateKey); err != nil { return err } return a.AgentConfigWriter.ChangeConfig(func(config agent.ConfigSetter) error { config.SetStateServingInfo(si) return nil }) }
// processCertDir iterates through the certDir looking for *.pem files. // Each pem file is read in turn and added to the pool. A count of the number // of successful certificates processed is returned. func processCertDir(pool *x509.CertPool) (count int) { fileInfo, err := os.Stat(certDir) if os.IsNotExist(err) { logger.Tracef("cert dir %q does not exist", certDir) return -1 } if err != nil { logger.Infof("unexpected error reading cert dir: %s", err) return -1 } if !fileInfo.IsDir() { logger.Infof("cert dir %q is not a directory", certDir) return -1 } matches, err := filepath.Glob(filepath.Join(certDir, "*.pem")) if err != nil { logger.Infof("globbing files failed: %s", err) return -1 } for _, match := range matches { data, err := ioutil.ReadFile(match) if err != nil { logger.Infof("error reading %q: %v", match, err) continue } certificate, err := cert.ParseCert(string(data)) if err != nil { logger.Infof("error parsing cert %q: %v", match, err) continue } pool.AddCert(certificate) count++ } return count }
func (s *ConfigSuite) TestGenerateControllerCertAndKey(c *gc.C) { // Add a cert. s.FakeHomeSuite.Home.AddFiles(c, gitjujutesting.TestFile{".ssh/id_rsa.pub", "rsa\n"}) for _, test := range []struct { caCert string caKey string sanValues []string }{{ caCert: testing.CACert, caKey: testing.CAKey, }, { caCert: testing.CACert, caKey: testing.CAKey, sanValues: []string{"10.0.0.1", "192.168.1.1"}, }} { certPEM, keyPEM, err := controller.GenerateControllerCertAndKey(test.caCert, test.caKey, test.sanValues) c.Assert(err, jc.ErrorIsNil) _, _, err = cert.ParseCertAndKey(certPEM, keyPEM) c.Check(err, jc.ErrorIsNil) err = cert.Verify(certPEM, testing.CACert, time.Now()) c.Assert(err, jc.ErrorIsNil) err = cert.Verify(certPEM, testing.CACert, time.Now().AddDate(9, 0, 0)) c.Assert(err, jc.ErrorIsNil) err = cert.Verify(certPEM, testing.CACert, time.Now().AddDate(10, 0, 1)) c.Assert(err, gc.NotNil) srvCert, err := cert.ParseCert(certPEM) c.Assert(err, jc.ErrorIsNil) sanIPs := make([]string, len(srvCert.IPAddresses)) for i, ip := range srvCert.IPAddresses { sanIPs[i] = ip.String() } c.Assert(sanIPs, jc.SameContents, test.sanValues) } }
// Validate ensures that config is a valid configuration. func Validate(c Config) error { if v, ok := c[IdentityPublicKey].(string); ok { var key bakery.PublicKey if err := key.UnmarshalText([]byte(v)); err != nil { return errors.Annotate(err, "invalid identity public key") } } if v, ok := c[IdentityURL].(string); ok { u, err := url.Parse(v) if err != nil { return errors.Annotate(err, "invalid identity URL") } // If we've got an identity public key, we allow an HTTP // scheme for the identity server because we won't need // to rely on insecure transport to obtain the public // key. if _, ok := c[IdentityPublicKey]; !ok && u.Scheme != "https" { return errors.Errorf("URL needs to be https when %s not provided", IdentityPublicKey) } } caCert, caCertOK := c.CACert() if !caCertOK { return errors.Errorf("missing CA certificate") } if _, err := cert.ParseCert(caCert); err != nil { return errors.Annotate(err, "bad CA certificate in configuration") } if uuid, ok := c[ControllerUUIDKey].(string); ok && !utils.IsValidUUIDString(uuid) { return errors.Errorf("controller-uuid: expected UUID, got string(%q)", uuid) } return nil }
func Open(info *Info, opts DialOpts) (*State, error) { if len(info.Addrs) == 0 { return nil, fmt.Errorf("no API addresses to connect to") } pool := x509.NewCertPool() xcert, err := cert.ParseCert(info.CACert) if err != nil { return nil, err } pool.AddCert(xcert) var environUUID string if info.EnvironTag != nil { environUUID = info.EnvironTag.Id() } // Dial all addresses at reasonable intervals. try := parallel.NewTry(0, nil) defer try.Kill() var addrs []string for _, addr := range info.Addrs { if strings.HasPrefix(addr, "localhost:") { addrs = append(addrs, addr) break } } if len(addrs) == 0 { addrs = info.Addrs } for _, addr := range addrs { err := dialWebsocket(addr, environUUID, opts, pool, try) if err == parallel.ErrStopped { break } if err != nil { return nil, err } select { case <-time.After(opts.DialAddressInterval): case <-try.Dead(): } } try.Close() result, err := try.Result() if err != nil { return nil, err } conn := result.(*websocket.Conn) logger.Infof("connection established to %q", conn.RemoteAddr()) client := rpc.NewConn(jsoncodec.NewWebsocket(conn), nil) client.Start() st := &State{ client: client, conn: conn, addr: conn.Config().Location.Host, serverRoot: "https://" + conn.Config().Location.Host, // why are the contents of the tag (username and password) written into the // state structure BEFORE login ?!? tag: toString(info.Tag), password: info.Password, certPool: pool, } if info.Tag != nil || info.Password != "" { if err := st.Login(info.Tag.String(), info.Password, info.Nonce); err != nil { conn.Close() return nil, err } } st.broken = make(chan struct{}) st.closed = make(chan struct{}) go st.heartbeatMonitor() return st, nil }