Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow displaying hardware keys prompts when relogin is in progress #48813

Merged
merged 14 commits into from
Nov 15, 2024

Conversation

gzdunek
Copy link
Contributor

@gzdunek gzdunek commented Nov 12, 2024

Fixes #48121

To solve the problem of not being able to prompt for the hardware key when an important dialog for relogin is already open, I enabled support for multiple important modals on the Electron side. Dialogs are hidden using CSS to preserve their state.
This allows unblocking the first important modal in the second important modal and then coming back to the first one (sounds like a line from Inception).

The main problem with hiding the dialogs is that I don't have access to parent elements for them. This means that I can't easily hide them from the ModalsHost level. Instead, each important dialog must handle its own hiding (by passing a flag to DialogConfirmation). To make this easier, I separated regular modals from important ones and added a JSDoc comment.

When it comes to importantModalSemaphore, I renamed it to singleImportantModalSemaphore to signal that it allows only one modal at a time. Hardware keys prompts don't use it.

Note to reviewers: best to review commit by commit and this is needed for 17.0.

@gzdunek gzdunek added no-changelog Indicates that a PR does not require a changelog entry backport/branch/v17 merge-for-v17 labels Nov 12, 2024
Copy link

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-48813.d3pp5qlev8mo18.amplifyapp.com

Comment on lines +39 to +41
// Since we cache the keys per slot, if two clusters specify the same one,
// the user will always see the prompt for the same cluster URI. For example, if you are logged into both
// cluster-a and cluster-b, the prompt will always say "Unlock hardware key to access cluster-b."
Copy link
Contributor Author

@gzdunek gzdunek Nov 12, 2024

Choose a reason for hiding this comment

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

I think that having a prompt per cluster was a mistake, since one prompt can be used for multiple clusters (I didn't realize that back then). While displaying a card name will require too much work, maybe we should just not display a cluster name in the dialog? I think a scenario where a user logs in with the same key to two clusters is more likely than the one where separate keys are used for different clusters.

@gzdunek gzdunek marked this pull request as ready for review November 12, 2024 15:17
@gzdunek gzdunek requested review from ravicious and Joerger November 12, 2024 15:17
@github-actions github-actions bot requested review from kimlisa and rudream November 12, 2024 15:18
Copy link
Contributor

@Joerger Joerger left a comment

Choose a reason for hiding this comment

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

Just reviewed the go portion

//
// We use a semaphore instead of a mutex in order to cancel important modals that
// are no longer relevant before acquisition.
//
// We use a waitSemaphore in order to make sure there is a clear transition between modals.
importantModalSemaphore *waitSemaphore
singleImportantModalSemaphore *waitSemaphore
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this importantModalSemaphore even necessary at all anymore? If we are allowing multiple important modals now, what makes hardware key pin prompts a special case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly, I'm a little afraid that we might run into some UX issues. For example, if you are in a middle of a relogin action, you wouldn't want to get an MFA prompt or something like that.
The difference between the semaphore here and the modal list on the fronted is that here a new modal waits for the previous one to complete, while on the fronted a new modal is placed on top of the existing one.

The special thing about hardware key prompts is that it's the only case where we need to complete an action started in one modal in another modal.

Copy link
Member

Choose a reason for hiding this comment

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

This is what I asked about in #48121 (comment).

With VNet + per-session MFA it's possible to issue two MFA prompts at the same time. Not only that, issuing a prompt immediately starts the Webauthn goroutine which prompts for key touch on hardware level. IIRC it's technically possible to have two concurrent touch prompts on hardware level, but we cannot really race it on the UI level.

For example, say you have two MFA prompts, 1 and 2. They get sent to the hardware key and to the Electron app in order. The Electron app would show a modal for prompt 2, while (AFAIR) the first touch of the key would resolve prompt 1.


That being said, I think the comment above singleImportantModalSemaphore needs a more detailed explanation.

However, we should use this feature only when necessary (for example, for hardware key prompts while relogin is in progress). In any other case, we should open one modal at a time.

At the moment, it's clear to us why it's necessary to allow two important modals for hardware key prompts while relogin is in progress. But the comment doesn't explain that and the next person reading this will have no idea about it. This could use what Grzegorz said in the last sentence of his comment above.

We could also bring up the example I gave with VNet and per-session MFA as to why singleImportantModalSemaphore is still needed.

Copy link
Contributor Author

@gzdunek gzdunek Nov 13, 2024

Choose a reason for hiding this comment

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

Thanks for providing the case with VNet + per-session MFA. I wasn't aware of it. I updated the comment with more detailed explanation. I hope it's more clear now.

EDIT: there's actually a problem with relogin + per-session MFA, see #48813 (comment). We will need there changes anyway.

Copy link
Contributor Author

@gzdunek gzdunek Nov 14, 2024

Choose a reason for hiding this comment

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

I will move the discussion from #48813 (comment) here.

So it looks like we need to remove the importantModalSempahore from the MFA prompt too. Instead we should have a separate one, just for MFA.

After removing importantModalSempahore from the MFA prompt, we will only have it in the relogin and headless auth prompts. This will effectively only prevent from showing a headless auth dialog when relogin is opened and vice-versa. Relogin also has its own mutex so it's not possible to have more than one relogin at a time.
Given all of that into account, maybe we should get rid of importantModalSempahore and only have a mutex/sempahore per prompt kind? Something like reloginMu, mfaMu, headlessWaitSemaphore. This should save us from conflicts between prompts of the same type.
Theoretically, we could get rid of a semaphore for the headless auth, but I think it's a nice feature that we show the prompts in the order of creation.

Copy link
Member

Choose a reason for hiding this comment

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

Single mutex per prompt kind sounds reasonable for now, I guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.
I still left waitSemaphore for headless auth. I remember that we wanted the user to see that a new headless request was received by having a slight delay between them. I'm not sure if this still makes sense, but I thought I'd leave it as is.

web/packages/design/src/Modal/Modal.tsx Outdated Show resolved Hide resolved

/**
* Renders an important dialog.
* Each dialog must implement a `hidden` prop which visually hides the dialog
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it wouldn't make sense to treat all dialogs uniformly, as in require all of them to use the hidden prop. This avoids having separate rules for regular or important dialog.

Think from the perspective of someone who doesn't contribute to Connect that often and wants to add a new modal. Asking them to understand the difference between having to add a hidden dialog or not having to do so is a big ask. They might not remember to do so, which creates a bug.

If all modals had hidden, we could in theory hide the regular modal if an important modal is shown. It should also be possible to somehow enforce this on the type level.

Does that sound reasonable? Would it be something that doesn't require too much work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeaaah, we could just add have hidden everywhere. While I'm not sure if currently we can enforce it by types, I believe it's less likely that someone will introduce a bug by omitting hidden.

If all modals had hidden, we could in theory hide the regular modal if an important modal is shown.

I did it too. Although sometimes you can see a blink of a regular dialog between important dialogs, overall it seems more "fluent" than with two layers of dialogs.

web/packages/teleterm/src/ui/ModalsHost/ModalsHost.tsx Outdated Show resolved Hide resolved
//
// We use a semaphore instead of a mutex in order to cancel important modals that
// are no longer relevant before acquisition.
//
// We use a waitSemaphore in order to make sure there is a clear transition between modals.
importantModalSemaphore *waitSemaphore
singleImportantModalSemaphore *waitSemaphore
Copy link
Member

Choose a reason for hiding this comment

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

This is what I asked about in #48121 (comment).

With VNet + per-session MFA it's possible to issue two MFA prompts at the same time. Not only that, issuing a prompt immediately starts the Webauthn goroutine which prompts for key touch on hardware level. IIRC it's technically possible to have two concurrent touch prompts on hardware level, but we cannot really race it on the UI level.

For example, say you have two MFA prompts, 1 and 2. They get sent to the hardware key and to the Electron app in order. The Electron app would show a modal for prompt 2, while (AFAIR) the first touch of the key would resolve prompt 1.


That being said, I think the comment above singleImportantModalSemaphore needs a more detailed explanation.

However, we should use this feature only when necessary (for example, for hardware key prompts while relogin is in progress). In any other case, we should open one modal at a time.

At the moment, it's clear to us why it's necessary to allow two important modals for hardware key prompts while relogin is in progress. But the comment doesn't explain that and the next person reading this will have no idea about it. This could use what Grzegorz said in the last sentence of his comment above.

We could also bring up the example I gave with VNet and per-session MFA as to why singleImportantModalSemaphore is still needed.

lib/teleterm/daemon/hardwarekeyprompt.go Outdated Show resolved Hide resolved
lib/teleterm/daemon/hardwarekeyprompt.go Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Something is not right. After the cert expires and I submit my credentials through the form, the hardware key starts to blink, but I don't see the prompt in the app. I can touch the key and then continue with the PIV prompts. But I don't see the original MFA prompt for the key touch. I do see the MFA prompt during regular login in an OSS cluster.

This is with require_session_mfa: hardware_key_touch_and_pin

piv-modals.mov

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Guess what, the MFA modal can't be opened because relogin is in progress...

This works during normal login, because in this case the login modal is a regular modal, when relogin is initiated from tshd, it is an important modal).
I believe the bug comes from #47153 (cc @Joerger).
I didn't catch it because I'm mostly using passwordless :(

So it looks like we need to remove the importantModalSempahore from the MFA prompt too. Instead we should have a separate one, just for MFA. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm I'm still getting this actually, let me take a look to see if I can understand the cause.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wait I think I just forgot to pull...

@gzdunek gzdunek requested review from Joerger and ravicious November 13, 2024 20:39
Copy link
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.

Ok, I tried it along with a couple of different login and relogin methods and I think this is going to work. At least until we add more modals that can be opened from tshd.

Though it all seems just a bit too fragile and I can't escape the feeling that there must be a better way to organize tshd <=> Electron communication in those cases to avoid this class of problems.

// We use a waitSemaphore to make sure there is a clear transition between modals.
// We allow a single headless auth prompt at a time.
headlessAuthSemaphore *waitSemaphore
// mfaMu prevents concurrent MFA prompts. These can happen when using VNet with per-session MFA.
// Issuing an MFA prompt starts the Webauthn goroutine which prompts for key touch on hardware level.
// Webauthn does not support concurrent prompts, so without this semaphore, one of the prompts would fail immediately.
Copy link
Member

Choose a reason for hiding this comment

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

I was going to ask if you have a proof for this, but I just quickly tested this by trying to log in through tsh and Connect at the same time and it seems to be true.

I did try this a looong time ago and back then it seemed to work, as in you'd submit two requests for touch and first touch would resolve the first request and the second touch would resolve the second one. But I think at the time I was still using U2F without knowing this.

@gzdunek
Copy link
Contributor Author

gzdunek commented Nov 15, 2024

Though it all seems just a bit too fragile and I can't escape the feeling that there must be a better way to organize tshd <=> Electron communication in those cases to avoid this class of problems.

Yeah, I agree. I think the main problem is that we don't really have a centralized place on the Electron side that manages these prompts. As I said in another comment, tshd should probably only send events, and Electron should be responsible for organizing them into dialogs, ensure that there are smooth transitions, decide what can be shown at the moment.

@gzdunek gzdunek added this pull request to the merge queue Nov 15, 2024
Merged via the queue into master with commit 7428128 Nov 15, 2024
45 checks passed
@gzdunek gzdunek deleted the gzdunek/fix-hardware-key-relogin-deadlock branch November 15, 2024 11:44
@public-teleport-github-review-bot

@gzdunek See the table below for backport results.

Branch Result
branch/v16 Failed
branch/v17 Create PR

gzdunek added a commit that referenced this pull request Dec 3, 2024
…48813)

* Convert `DialogConfirmation` to TS

* Allow `DialogConfirmation` and `Modal` to be hidden using CSS while closed

* Allow hiding all dialogs that are displayed as important

* Allow displaying multiple important dialogs, separate regular and important dialogs and get rid of `DialogNone`

* Pass `hidden` prop to important dialogs

* Rename `importantModalSemaphore` to `singleImportantModalSemaphore`

* Remove semaphores from hardware key prompts

* `keepMounted` -> `keepInDOMAfterClose`

* Remove the explicit value from `keepInDOMAfterClose`

* Revert splitting dialogs into regular and important ones, pass `hidden` to all of them, hide regular modal when important one is visible

* Use random id as a modal key

* Improve `singleImportantModalSemaphore` comment

* Improve `NewHardwareKeyPromptConstructor` comment

* Do not acquire important modal semaphore in MFA prompt and relogin, give each prompt its own mutex/semaphore

(cherry picked from commit 7428128)
gzdunek added a commit that referenced this pull request Dec 4, 2024
…48813)

* Convert `DialogConfirmation` to TS

* Allow `DialogConfirmation` and `Modal` to be hidden using CSS while closed

* Allow hiding all dialogs that are displayed as important

* Allow displaying multiple important dialogs, separate regular and important dialogs and get rid of `DialogNone`

* Pass `hidden` prop to important dialogs

* Rename `importantModalSemaphore` to `singleImportantModalSemaphore`

* Remove semaphores from hardware key prompts

* `keepMounted` -> `keepInDOMAfterClose`

* Remove the explicit value from `keepInDOMAfterClose`

* Revert splitting dialogs into regular and important ones, pass `hidden` to all of them, hide regular modal when important one is visible

* Use random id as a modal key

* Improve `singleImportantModalSemaphore` comment

* Improve `NewHardwareKeyPromptConstructor` comment

* Do not acquire important modal semaphore in MFA prompt and relogin, give each prompt its own mutex/semaphore

(cherry picked from commit 7428128)
github-merge-queue bot pushed a commit that referenced this pull request Dec 4, 2024
…ess (#49702)

* Allow displaying hardware keys prompts when relogin is in progress (#48813)

* Convert `DialogConfirmation` to TS

* Allow `DialogConfirmation` and `Modal` to be hidden using CSS while closed

* Allow hiding all dialogs that are displayed as important

* Allow displaying multiple important dialogs, separate regular and important dialogs and get rid of `DialogNone`

* Pass `hidden` prop to important dialogs

* Rename `importantModalSemaphore` to `singleImportantModalSemaphore`

* Remove semaphores from hardware key prompts

* `keepMounted` -> `keepInDOMAfterClose`

* Remove the explicit value from `keepInDOMAfterClose`

* Revert splitting dialogs into regular and important ones, pass `hidden` to all of them, hide regular modal when important one is visible

* Use random id as a modal key

* Improve `singleImportantModalSemaphore` comment

* Improve `NewHardwareKeyPromptConstructor` comment

* Do not acquire important modal semaphore in MFA prompt and relogin, give each prompt its own mutex/semaphore

(cherry picked from commit 7428128)

* Re-add `keepInDOMAfterClose` to `Modal.jsx`

* Fix a mistake causing document reopen dialog to be always hidden (#49061)

(cherry picked from commit 7b520cc)

* Change `dialogCss` type to `any` since there is no `StyleFunction` available in `styled-components` in branch/v16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Relogin and hardware key dialogs deadlock in Connect
3 participants