func (k *Kontrol) handleRegister(r *kite.Request) (interface{}, error) { k.log.Info("Register request from: %s", r.Client.Kite) if r.Args.One().MustMap()["url"].MustString() == "" { return nil, errors.New("invalid url") } var args struct { URL string `json:"url"` } r.Args.One().MustUnmarshal(&args) if args.URL == "" { return nil, errors.New("empty url") } // Only accept requests with kiteKey because we need this info // for generating tokens for this kite. if r.Auth.Type != "kiteKey" { return nil, fmt.Errorf("Unexpected authentication type: %s", r.Auth.Type) } t, err := jwt.Parse(r.Auth.Key, kitekey.GetKontrolKey) if err != nil { return nil, err } publicKey, ok := t.Claims["kontrolKey"].(string) if !ok { return nil, errors.New("public key is not passed") } var keyPair *KeyPair var newKey bool // check if the key is valid and is stored in the key pair storage, if not // check if there is a new key we can use. keyPair, err = k.keyPair.GetKeyFromPublic(strings.TrimSpace(publicKey)) if err != nil { newKey = true keyPair, err = k.pickKey(r) if err != nil { return nil, err // nothing to do here .. } } kiteURL := args.URL remote := r.Client if err := validateKiteKey(&remote.Kite); err != nil { return nil, err } value := &kontrolprotocol.RegisterValue{ URL: kiteURL, KeyID: keyPair.ID, } // Register first by adding the value to the storage. Return if there is // any error. if err := k.storage.Upsert(&remote.Kite, value); err != nil { k.log.Error("storage add '%s' error: %s", remote.Kite, err) return nil, errors.New("internal error - register") } every := onceevery.New(UpdateInterval) ping := make(chan struct{}, 1) closed := false updaterFunc := func() { for { select { case <-ping: k.log.Debug("Kite is active, got a ping %s", remote.Kite) every.Do(func() { k.log.Debug("Kite is active, updating the value %s", remote.Kite) err := k.storage.Update(&remote.Kite, value) if err != nil { k.log.Error("storage update '%s' error: %s", remote.Kite, err) } }) case <-time.After(HeartbeatInterval + HeartbeatDelay): k.log.Debug("Kite didn't sent any heartbeat %s.", remote.Kite) every.Stop() closed = true return } } } go updaterFunc() heartbeatArgs := []interface{}{ HeartbeatInterval / time.Second, dnode.Callback(func(args *dnode.Partial) { k.log.Debug("Kite send us an heartbeat. %s", remote.Kite) k.clientLocks.Get(remote.Kite.ID).Lock() defer k.clientLocks.Get(remote.Kite.ID).Unlock() select { case ping <- struct{}{}: default: } // seems we miss a heartbeat, so start it again! if closed { closed = false k.log.Warning("Updater was closed, but we are still getting heartbeats. Starting again %s", remote.Kite) // it might be removed because the ttl cleaner would come // before us, so try to add it again, the updater will than // continue to update it afterwards. k.storage.Upsert(&remote.Kite, value) go updaterFunc() } }), } // now trigger the remote kite so it sends us periodically an heartbeat remote.GoWithTimeout("kite.heartbeat", 4*time.Second, heartbeatArgs...) k.log.Info("Kite registered: %s", remote.Kite) remote.OnDisconnect(func() { k.log.Info("Kite disconnected: %s", remote.Kite) every.Stop() }) // send response back to the kite, also send the new public Key if it's exist p := &protocol.RegisterResult{URL: args.URL} if newKey { p.PublicKey = keyPair.Public } return p, nil }
// NewKlient returns a new Klient instance func NewKlient(conf *KlientConfig) (*Klient, error) { // this is our main reference to count and measure metrics for the klient // we count only those methods, please add/remove methods here that will // reset the timer of a klient. usg := usage.NewUsage(map[string]bool{ "fs.readDirectory": true, "fs.glob": true, "fs.readFile": true, "fs.writeFile": true, "fs.uniquePath": true, "fs.getInfo": true, "fs.setPermissions": true, "fs.remove": true, "fs.rename": true, "fs.createDirectory": true, "fs.move": true, "fs.copy": true, "webterm.getSessions": true, "webterm.connect": true, "webterm.killSession": true, "webterm.killSessions": true, "webterm.rename": true, "exec": true, "klient.share": true, "klient.unshare": true, "klient.shared": true, "sshkeys.List": true, "sshkeys.Add": true, "sshkeys.Delete": true, "storage.Get": true, "storage.Set": true, "storage.Delete": true, "log.upload": true, // "docker.create": true, // "docker.connect": true, // "docker.stop": true, // "docker.start": true, // "docker.remove": true, // "docker.list": true, }) // TODO(rjeczalik): Once klient installation method is reworked, // ensure flags are stored alongside konfig and do not // overwrite konfig here. if conf.KontrolURL != "" { konfig.Konfig.KontrolURL = conf.KontrolURL } // NOTE(rjeczalik): For backward-compatibility with old klient, // remove once not needed. if u, err := url.Parse(konfig.Konfig.KontrolURL); err == nil && konfig.Konfig.KontrolURL != "" { u.Path = "" konfig.Konfig.Endpoints.Koding = cfg.NewEndpointURL(u) } if conf.TunnelKiteURL != "" { u, err := url.Parse(conf.TunnelKiteURL) if err != nil { return nil, err } konfig.Konfig.Endpoints.Tunnel.Public.URL = u } k := newKite(conf) k.Config.VerifyAudienceFunc = verifyAudience if k.Config.KontrolURL == "" || k.Config.KontrolURL == "http://127.0.0.1:3000/kite" || !konfig.Konfig.Endpoints.Kontrol().Equal(konfig.Builtin.Endpoints.Kontrol()) { k.Config.KontrolURL = konfig.Konfig.Endpoints.Kontrol().Public.String() } term := terminal.New(k.Log, conf.ScreenrcPath) term.InputHook = usg.Reset db, err := openBoltDB(configstore.CacheOptions("klient")) if err != nil { k.Log.Warning("Couldn't open BoltDB: %s", err) } up := uploader.New(&uploader.Options{ KeygenURL: konfig.Konfig.Endpoints.Kloud().Public.String(), Kite: k, Bucket: conf.logBucketName(), Region: conf.logBucketRegion(), DB: db, Log: k.Log, }) vagrantOpts := &vagrant.Options{ Home: conf.VagrantHome, DB: db, // nil is ok, fallbacks to in-memory storage Log: k.Log, Debug: conf.Debug, Output: up.Output, } tunOpts := &tunnel.Options{ DB: db, Log: k.Log, Kite: k, NoProxy: conf.NoProxy, TunnelKiteURL: konfig.Konfig.Endpoints.Tunnel.Public.String(), } t, err := tunnel.New(tunOpts) if err != nil { log.Fatal(err) } if conf.UpdateInterval < time.Minute { k.Log.Warning("Update interval can't be less than one minute. Setting to one minute.") conf.UpdateInterval = time.Minute } // TODO(rjeczalik): Enable after TMS-848. // mountEvents := make(chan *mount.Event) remoteOpts := &remote.RemoteOptions{ Kite: k, Log: k.Log, Storage: storage.New(db), // EventSub: mountEvents, } machinesOpts := &machinegroup.GroupOpts{ Storage: storage.NewEncodingStorage(db, []byte("machines")), Builder: mclient.NewKiteBuilder(k), DynAddrInterval: 2 * time.Second, PingInterval: 15 * time.Second, } machines, err := machinegroup.New(machinesOpts) if err != nil { k.Log.Fatal("Cannot initialize machine group: %s", err) } c := k.NewClient(konfig.Konfig.Endpoints.Kloud().Public.String()) c.Auth = &kite.Auth{ Type: "kiteKey", Key: k.Config.KiteKey, } kloud := &apiutil.LazyKite{ Client: c, } restClient := httputil.DefaultRestClient(konfig.Konfig.Debug) restClient.Transport = &api.Transport{ RoundTripper: restClient.Transport, AuthFunc: (&apiutil.KloudAuth{ Kite: kloud, Storage: &apiutil.Storage{ &cfg.Cache{ EncodingStorage: storage.NewEncodingStorage(db, []byte("klient")), }, }, }).Auth, Log: k.Log.(logging.Logger), } kl := &Klient{ kite: k, collab: collaboration.New(db), // nil is ok, fallbacks to in memory storage storage: storage.New(db), // nil is ok, fallbacks to in memory storage tunnel: t, vagrant: vagrant.NewHandlers(vagrantOpts), // docker: docker.New("unix://var/run/docker.sock", k.Log), terminal: term, usage: usg, log: k.Log, config: conf, remote: remote.NewRemote(remoteOpts), uploader: up, machines: machines, updater: &Updater{ Endpoint: conf.UpdateURL, Interval: conf.UpdateInterval, CurrentVersion: conf.Version, KontrolURL: k.Config.KontrolURL, // MountEvents: mountEvents, Log: k.Log, }, logUploadDelay: 3 * time.Minute, presence: &presence.Client{ Endpoint: konfig.Konfig.Endpoints.Social().Public.WithPath("presence").URL, Client: restClient, }, presenceEvery: onceevery.New(1 * time.Hour), kloud: kloud, } kl.kite.OnRegister(kl.updateKiteKey) // Close all active sessions of the current. Do not close it immediately, // instead of give some time so users can safely exit. If the user // reconnects again the timer will be stopped so we don't unshare for // network hiccups accidentally. kl.collabCloser = NewDeferTime(time.Minute, func() { sharedUsers, err := kl.collab.GetAll() if err != nil { kl.log.Warning("Couldn't unshare users: %s", err) return } if len(sharedUsers) == 0 { return } kl.log.Info("Unsharing users '%s'", sharedUsers) for user, option := range sharedUsers { // dont touch permanent users if option.Permanent { kl.log.Info("User is permanent, avoiding it: %q", user) continue } if err := kl.collab.Delete(user); err != nil { kl.log.Warning("Couldn't delete user from storage: %s", err) } kl.terminal.CloseSessions(user) } }) // This is important, don't forget it kl.RegisterMethods() return kl, nil }
func (k *Kontrol) HandleRegister(r *kite.Request) (interface{}, error) { k.log.Info("Register request from: %s", r.Client.Kite) // Only accept requests with kiteKey because we need this info // for generating tokens for this kite. if r.Auth.Type != "kiteKey" { return nil, fmt.Errorf("Unexpected authentication type: %s", r.Auth.Type) } var args struct { URL string `json:"url"` } if err := r.Args.One().Unmarshal(&args); err != nil { return nil, err } if args.URL == "" { return nil, errors.New("empty url") } if _, err := url.Parse(args.URL); err != nil { return nil, fmt.Errorf("invalid register URL: %s", err) } res := &protocol.RegisterResult{ URL: args.URL, } ex := &kitekey.Extractor{ Claims: &kitekey.KiteClaims{}, } t, err := jwt.ParseWithClaims(r.Auth.Key, ex.Claims, ex.Extract) if err != nil { return nil, err } var keyPair *KeyPair var origKey = ex.Claims.KontrolKey // check if the key is valid and is stored in the key pair storage, if not // check if there is a new key we can use. keyPair, res.KiteKey, err = k.getOrUpdateKeyPub(ex.Claims.KontrolKey, t, r) if err != nil { return nil, err } if origKey != keyPair.Public { // NOTE(rjeczalik): updates public key for old kites, new kites // expect kite key to be updated res.PublicKey = keyPair.Public } if err := validateKiteKey(&r.Client.Kite); err != nil { return nil, err } value := &kontrolprotocol.RegisterValue{ URL: args.URL, KeyID: keyPair.ID, } // Register first by adding the value to the storage. Return if there is // any error. if err := k.storage.Upsert(&r.Client.Kite, value); err != nil { k.log.Error("storage add '%s' error: %s", &r.Client.Kite, err) return nil, errors.New("internal error - register") } every := onceevery.New(UpdateInterval) ping := make(chan struct{}, 1) closed := int32(0) kiteCopy := r.Client.Kite updaterFunc := func() { for { select { case <-k.closed: return case <-ping: k.log.Debug("Kite is active, got a ping %s", &kiteCopy) every.Do(func() { k.log.Debug("Kite is active, updating the value %s", &kiteCopy) err := k.storage.Update(&kiteCopy, value) if err != nil { k.log.Error("storage update '%s' error: %s", &kiteCopy, err) } }) case <-time.After(HeartbeatInterval + HeartbeatDelay): k.log.Debug("Kite didn't sent any heartbeat %s.", &kiteCopy) atomic.StoreInt32(&closed, 1) return } } } go updaterFunc() heartbeatArgs := []interface{}{ HeartbeatInterval / time.Second, dnode.Callback(func(args *dnode.Partial) { k.log.Debug("Kite send us an heartbeat. %s", &kiteCopy) k.clientLocks.Get(kiteCopy.ID).Lock() defer k.clientLocks.Get(kiteCopy.ID).Unlock() select { case ping <- struct{}{}: default: } // seems we miss a heartbeat, so start it again! if atomic.CompareAndSwapInt32(&closed, 1, 0) { k.log.Warning("Updater was closed, but we are still getting heartbeats. Starting again %s", &kiteCopy) // it might be removed because the ttl cleaner would come // before us, so try to add it again, the updater will than // continue to update it afterwards. k.storage.Upsert(&kiteCopy, value) go updaterFunc() } }), } // now trigger the remote kite so it sends us periodically an heartbeat resp := r.Client.GoWithTimeout("kite.heartbeat", 4*time.Second, heartbeatArgs...) go func() { if err := (<-resp).Err; err != nil { k.log.Error("failed requesting heartbeats from %q kite: %s", kiteCopy.Name, err) } }() k.log.Info("Kite registered: %s", &r.Client.Kite) clientKite := r.Client.Kite.String() r.Client.OnDisconnect(func() { k.log.Info("Kite disconnected: %s", clientKite) }) return res, nil }