Conversation
f8223aa to
6d4fc09
Compare
2b6aaff to
3213af8
Compare
96bbc52 to
6b709aa
Compare
3213af8 to
7877dff
Compare
ravicious
left a comment
There was a problem hiding this comment.
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.
4a08360 to
b6b639b
Compare
260d440 to
95f1532
Compare
b6b639b to
1c5ffe2
Compare
44082a6 to
2d2abe1
Compare
| // 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 { |
There was a problem hiding this comment.
I think it won't work correctly in the following scenario:
- Log in to a cluster.
- Wait for a cert to expire.
- 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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. 🤔
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Just to clarify, after the cert expires, the watcher still works and receives updates from the cluster, is that correct?
There was a problem hiding this comment.
Right, until the client is closed or receives an error over the connection (Auth down, backend down, etc.).
| // 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 { |
There was a problem hiding this comment.
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.
| // 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 { |
There was a problem hiding this comment.
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. 🤔
5b896d5 to
18b1d52
Compare
* Add headless watcher to tshd daemon service. * Add SendPendingHeadlessAuthentication rpc to tshd events service. * Add UpdateHeadlessAuthenticationState rpc to the daemon service.
06ae6c7 to
6df43c7
Compare
ravicious
left a comment
There was a problem hiding this comment.
Looks good though obv I didn't test it, I'm leaving that for the second part which implements the UI for it.
| // 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) |
There was a problem hiding this comment.
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.
| // 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 { |
There was a problem hiding this comment.
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)
9ff95c8 to
6b44714
Compare
| if err := s.StopHeadlessWatcher(uri); err != nil { | ||
| return trace.Wrap(err) | ||
| } |
There was a problem hiding this comment.
ClusterLogout needs to ignore NotFound from StopHeadlessWatcher.
ClusterLogout can be called on a cluster with expired certs which doesn't have a watcher active.
There was a problem hiding this comment.
Will fix this in the other PR, thanks
* 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.
* * 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)
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:
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