Skip to content

Teleport Connect headless watcher#28844

Merged
Joerger merged 2 commits intomasterfrom
joerger/teleport-connect-headless-watcher
Jul 19, 2023
Merged

Teleport Connect headless watcher#28844
Joerger merged 2 commits intomasterfrom
joerger/teleport-connect-headless-watcher

Conversation

@Joerger
Copy link
Copy Markdown
Contributor

@Joerger Joerger commented Jul 8, 2023

This PR adds a headless authentication watcher to Teleport Connect to catch any pending headless authentications for current login sessions, as well as an endpoint for the Electron App to approve/deny a headless authentication.

In this PR, we simply send the notification to the Electron App but don't handle it.

In a follow up PR, the Electron App will be updated to display two modals, similar to that of the web UI to:

  1. approve or deny the headless login
  2. prompt for MFA (when approving)

Note: the MFA prompt will be similar to that of the login flow. The Electron App will display an MFA prompt modal, and then call rpc UpdateHeadlessAuthenticationState(state=approve). Once the rpc succeeds, it will display a success modal or close the prompt.

Updates #27137

@Joerger Joerger requested a review from ravicious July 8, 2023 03:24
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from f8223aa to 6d4fc09 Compare July 10, 2023 20:22
@Joerger Joerger changed the base branch from master to joerger/refactor-db-cert-reissuer July 10, 2023 20:28
@Joerger Joerger marked this pull request as ready for review July 11, 2023 01:31
@github-actions github-actions Bot requested review from kimlisa and ryanclark July 11, 2023 01:32
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from 2b6aaff to 3213af8 Compare July 11, 2023 01:34
@Joerger Joerger force-pushed the joerger/refactor-db-cert-reissuer branch from 96bbc52 to 6b709aa Compare July 11, 2023 01:35
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from 3213af8 to 7877dff Compare July 11, 2023 01:36
@ravicious ravicious requested review from gzdunek and removed request for kimlisa and ryanclark July 11, 2023 13:59
Copy link
Copy Markdown
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to take another look at it tomorrow, I didn't have time to look at cluster_headless.go. But the part in the daemon looks solid.

Comment thread lib/teleterm/daemon/daemon.go Outdated
Comment thread proto/teleport/lib/teleterm/v1/service.proto Outdated
Comment thread proto/teleport/lib/teleterm/v1/tshd_events_service.proto Outdated
Comment thread lib/teleterm/daemon/daemon_headless_watcher.go Outdated
Comment thread proto/teleport/lib/teleterm/v1/service.proto Outdated
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from 4a08360 to b6b639b Compare July 11, 2023 20:11
@Joerger Joerger force-pushed the joerger/refactor-db-cert-reissuer branch from 260d440 to 95f1532 Compare July 11, 2023 20:22
@Joerger Joerger requested a review from ravicious July 11, 2023 20:23
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from b6b639b to 1c5ffe2 Compare July 11, 2023 20:34
Base automatically changed from joerger/refactor-db-cert-reissuer to master July 11, 2023 21:12
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch 2 times, most recently from 44082a6 to 2d2abe1 Compare July 12, 2023 00:33
// startHeadlessWatcher starts a background process to watch for pending headless
// authentications for this cluster.
func (s *Service) startHeadlessWatcher(cluster *clusters.Cluster) error {
if _, ok := s.headlessWatcherClosers[cluster.URI.String()]; ok {
Copy link
Copy Markdown
Contributor

@gzdunek gzdunek Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it won't work correctly in the following scenario:

  1. Log in to a cluster.
  2. Wait for a cert to expire.
  3. Try to log in again.

Step 3 will fail with the error saying that the watcher already exists, won't it? I guess we should just close the old watcher before starting the new one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think catching AlreadyExists in StartHeadlessWatcher and simply logging the fact should do the job. Its plural equivalent, StartHeadlessWatchers, should definitely error if a watcher already exists.

Copy link
Copy Markdown
Contributor Author

@Joerger Joerger Jul 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update it so that when the watcher goroutine loop ends, it is deleted from the map. This way, if the certs expire, the watcher should clean itself up.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as I understand, the current implementation assumes that the watcher goroutine loop ends after the cert expires, right? How does this happen? It's not clear for me just from looking at the code. 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right...I forget that a client can still function with expired certs, just can't redial. Updated to have StartHeadlessWatcher stop and replace the previous watcher if there is one.

Copy link
Copy Markdown
Contributor

@gzdunek gzdunek Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what is going to happen when the headless auth request is created and my certs are expired? Can a watcher on an expired client receive it? (I don't have much knowledge about watchers)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The watcher will still work and send the headless authn to the Electron App. If the user hasn't logged in however, the Electron app will fail to approve/deny the headless authn, trigger relogin, and retry. At this point, the headless authn can be handled and the old headless watcher will be replaced. So in a roundabout way, the Headless watcher system should handle relogin surprisingly well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to clarify, after the cert expires, the watcher still works and receives updates from the cluster, is that correct?

Copy link
Copy Markdown
Contributor Author

@Joerger Joerger Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, until the client is closed or receives an error over the connection (Auth down, backend down, etc.).

Comment thread lib/teleterm/clusters/cluster_headless.go Outdated
Comment thread lib/teleterm/clusters/cluster_headless.go Outdated
Comment thread lib/teleterm/daemon/client.go Outdated
Comment thread lib/teleterm/daemon/client.go Outdated
// startHeadlessWatcher starts a background process to watch for pending headless
// authentications for this cluster.
func (s *Service) startHeadlessWatcher(cluster *clusters.Cluster) error {
if _, ok := s.headlessWatcherClosers[cluster.URI.String()]; ok {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think catching AlreadyExists in StartHeadlessWatcher and simply logging the fact should do the job. Its plural equivalent, StartHeadlessWatchers, should definitely error if a watcher already exists.

Comment thread lib/teleterm/daemon/daemon_headless_watcher.go Outdated
Comment thread lib/teleterm/daemon/daemon_headless_watcher.go Outdated
Comment thread lib/teleterm/daemon/daemon_headless_watcher.go Outdated
// startHeadlessWatcher starts a background process to watch for pending headless
// authentications for this cluster.
func (s *Service) startHeadlessWatcher(cluster *clusters.Cluster) error {
if _, ok := s.headlessWatcherClosers[cluster.URI.String()]; ok {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So as I understand, the current implementation assumes that the watcher goroutine loop ends after the cert expires, right? How does this happen? It's not clear for me just from looking at the code. 🤔

@Joerger Joerger changed the title Implement Teleport Connect headless watcher Teleport Connect headless watcher Jul 14, 2023
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from 5b896d5 to 18b1d52 Compare July 14, 2023 01:34
Comment thread lib/teleterm/clusters/cluster_headless.go Outdated
* Add headless watcher to tshd daemon service.

* Add SendPendingHeadlessAuthentication rpc to tshd events service.

* Add UpdateHeadlessAuthenticationState rpc to the daemon service.
@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from 06ae6c7 to 6df43c7 Compare July 17, 2023 19:06
@Joerger Joerger requested review from gzdunek and ravicious July 17, 2023 19:08
Copy link
Copy Markdown
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good though obv I didn't test it, I'm leaving that for the second part which implements the UI for it.

Comment on lines +292 to +299
// Stop and restart the watcher twice to simulate logout + login + relogin. Ensure the watcher catches events.

err = daemonService.StopHeadlessWatcher(cluster.URI.String())
require.NoError(t, err)
err = daemonService.StartHeadlessWatcher(cluster.URI.String())
require.NoError(t, err)
err = daemonService.StartHeadlessWatcher(cluster.URI.String())
require.NoError(t, err)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be better to use actual login/logout handlers, but the last time I tried to do that I realized there's no way to get a password for a user out of those test helpers.

Comment thread integration/teleterm_test.go
Comment thread lib/teleterm/apiserver/handler/handler_headless.go Outdated
Comment thread lib/teleterm/clusters/cluster_headless.go Outdated
Comment thread lib/teleterm/clusters/cluster_headless.go
Comment thread integration/teleterm_test.go
Comment thread integration/teleterm_test.go
Comment thread integration/teleterm_test.go Outdated
Comment thread lib/teleterm/daemon/daemon_headless_watcher.go
Comment thread lib/teleterm/apiserver/handler/handler_headless.go Outdated
Comment thread lib/teleterm/clusters/cluster_headless.go Outdated
// startHeadlessWatcher starts a background process to watch for pending headless
// authentications for this cluster.
func (s *Service) startHeadlessWatcher(cluster *clusters.Cluster) error {
if _, ok := s.headlessWatcherClosers[cluster.URI.String()]; ok {
Copy link
Copy Markdown
Contributor

@gzdunek gzdunek Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And what is going to happen when the headless auth request is created and my certs are expired? Can a watcher on an expired client receive it? (I don't have much knowledge about watchers)

@Joerger Joerger force-pushed the joerger/teleport-connect-headless-watcher branch from 9ff95c8 to 6b44714 Compare July 19, 2023 02:39
@Joerger Joerger enabled auto-merge July 19, 2023 02:39
@Joerger Joerger added this pull request to the merge queue Jul 19, 2023
Merged via the queue into master with commit 2099ccc Jul 19, 2023
@Joerger Joerger deleted the joerger/teleport-connect-headless-watcher branch July 19, 2023 03:14
@public-teleport-github-review-bot
Copy link
Copy Markdown

@Joerger See the table below for backport results.

Branch Result
branch/v12 Failed
branch/v13 Failed

Comment on lines +225 to +227
if err := s.StopHeadlessWatcher(uri); err != nil {
return trace.Wrap(err)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClusterLogout needs to ignore NotFound from StopHeadlessWatcher.

ClusterLogout can be called on a cluster with expired certs which doesn't have a watcher active.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix this in the other PR, thanks

Joerger added a commit that referenced this pull request Aug 1, 2023
* Implement headless watcher backend for Teleport Connect.

* Add headless watcher to tshd daemon service.

* Add SendPendingHeadlessAuthentication rpc to tshd events service.

* Add UpdateHeadlessAuthenticationState rpc to the daemon service.

* Address comments.
github-merge-queue Bot pushed a commit that referenced this pull request Aug 2, 2023
* * Enable headless authentication event watch. (#28234)

* Add WatchPendingHeadlessAuthentications rpc.

* Fix headless authentication matching logic for watcher (#28843)

* Fix headless authentication matching logic for watcher and add test.

* Move hasWatchPermissionForKind to a separate function.

* Clean up hasWatchPermissionForKind.

* Cleanup test code with suggestions from review.

* Refactor Gateway Cert Reissuer and tshd events client (#28782)

* - Move tshd events client into the daemon service.

- Replace gatway cert reissuer with a more reusable retryWithRelogin
method.

* Resolve comments.

* Teleport Connect headless watcher (#28844)

* Implement headless watcher backend for Teleport Connect.

* Add headless watcher to tshd daemon service.

* Add SendPendingHeadlessAuthentication rpc to tshd events service.

* Add UpdateHeadlessAuthenticationState rpc to the daemon service.

* Address comments.

* Tune Headless Watcher retry logic in Teleport Connect (#29410)

* Reduce headless watcher max backoff period to 90s; Propogate watcher error properly; Don't retry on not implemented error.

* Stop watcher if it wasn't stopped already.

* Implement headless watcher approval logic in the Electron App. (#29097)

* Fix uncaught merge conflict.

* Fix call count race condition; Fix grpc server stop race condition; Make timeout less aggressive. (#29880)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants