Fixes an issue with ContextReader on `bash` where abandoned password reads cause
the terminal to remain "locked" even after `tsh` exits. This happens on
interrupts but also on regular exit if the user picks a security key in the dual
security key/OTP login prompt.
Doesn't seem to affect shells like `zsh` or `fish`.
Repro steps:
```shell
$ bash
# repro #1
$ ./tsh login --proxy=example.com
> Enter password for Teleport user llama: <CTRL-C>
# shell is now locked
# repro #2
# (Use an account with security key and OTP MFA.)
$ ./tsh login --proxy=example.com
> Enter password for Teleport user llama: <enters password>
> Tap any security key or enter a code from a OTP device <taps security key>
# shell is now locked
```
* Restore terminal state on interrupt or exit
* Notify prompt on tsh exit
* Review: context.WithTimeout and Notify on os.Exit
Adding a "marker" to Keychain labels lets us detect unexpected entries (which I
did see happen) and gives us flexibility to change formats in the future.
A marker in the "label" field seems to be enough to achieve the purposes above,
so I stopped at that. Added a couple minor UX tweaks as well.
#9160
* Use a marker for Keychain labels
* Tweak: Rename "Key Handle" to "Credential ID" for consistency
* Tweak: Do not prompt MFA/OTP if doing Touch ID
* Use trace errors
Introduce the prompt.Password function and use it consistently whenever we read
a password, OTP or PIN.
The PR makes it easy to support PINs during MFA authentication, so now we do so.
It also adds the capability to mock prompt.Stdin() for tests, adding a uniform
way to fake user input in tests without having to swap functions.
Complements #10953 and #9160.
* Prompt for OTPs as passwords (take 1), read PINs on MFA authentication
* Add the prompt.Password method
* Add Stdin mocking capabilities to the prompt package
* Delegate password reads to prompt.Stdin().ReadPassword()
* Remove stdinHijack from PromptMFAChallenge
* Change api_login_test.go to FakeReader
* Change tsh_test.go to FakeReader
* Retire client.ReadPassword in favor of prompt.Password
This changes prompt.ContextReader in the following ways:
Reads only happen as a response to Read methods being called. This allows
ContextReader to coexist with other readers as long as no reads are abandoned.
ReadPassword is now available, the underlying implementation being
term.ReadPassword. An abandoned password read may be turned into a clean read.
This gives us some UX flexibility when callers abandon password reads (looking
at you, PromptMFAChallenge). Turning clean reads into password reads is not
supported. It's tricky and I have a few ideas, but it's not paramount at this
moment.
This solves the woes caused by abandoned OTP reads followed by PIN reads in
different packages, such as client.PromptMFAChallenge followed by tsh mfa add's
implementation.
#9160
* Move ContextReader to its own file
* Refactor ContextReader and implement ReadPassword
* Test ReadPassword
* Fix typos
* Remove prompt.StdinSync()
prompt.Stdin() has the same behavior for non-abandoned reads.
* Group /x/term methods under a type
Seamlessly change the public API of lib/auth/webauthncli to use the
libfido2-backed implementation, as long as the binary was compiled with the
libfido2 build tag.
A few adjustments are necessary to "wancli" methods to allow users to provide
prompt callbacks and to return the credential user (not applicable here, but
will be in following PRs).
Additional changes are made to tsh mfa add in order to avoid stdin hijacking by
ContextReader, since we now may require PIN reads for authenticators.
#9160
* Move U2F logic to u2f_* files
* Split U2F API from general l/a/webauthncli API
* Move FIDO2 public API to fido2_common.go, introduce IsFIDO2Available
* Introduce prompt.SyncReader
Sync reads allow prompt calls to be mixed with term.ReadPassword calls.
* Wire FIDO2 into MFA login
* Wire FIDO2 into MFA registration
This is a collection of a few small changes related to user presentation of
WebAuthn/MFA in tsh. The intent is to make tsh language match ongoing Web UI
changes.
* Make use of preferred MFA in `tsh mfa add`
* Tweak prompt error message
Old:
ERROR: "U2F\n" is not a valid option, please specify one of [TOTP, WEBAUTHN]
New:
ERROR: "U2F" is not a valid option, please specify one of [TOTP, WEBAUTHN]
* Directly mention WebAuthn when prompting for challenges
* Fix typo on godoc
* Print devices sorted by name on `tsh mfa ls`
* Address origin validation TODOs
For registration and a few other use cases the original error is relayed
back to the client, so there is already a good indicator that it failed
due to origin woes.
For login we purposefully obsfucate errors. To address that I've added a
few debug-level server-side log statements; it seems best to not make
further changes in this case.
* Amend preferred device type logic
* Adjust PromptMFAChallenge message
Implement context-based cancellation in `/lib/utils/prompt`, for MFA
prompts.
This fixes the following scenario:
```sh
User has both OTP and U2F devices registered.
$ tsh mfa ls
Name Type Added at Last used
----- ---- ----------------------------- -----------------------------
otp TOTP Wed, 21 Apr 2021 19:41:44 UTC Wed, 21 Apr 2021 19:44:32 UTC
usb-a U2F Wed, 21 Apr 2021 19:44:34 UTC Wed, 21 Apr 2021 19:44:34 UTC
Add a new OTP device, using existing U2F device:
$ tsh mfa add
Choose device type [TOTP, U2F]: totp
Enter device name: otp2
Tap any *registered* security key or enter a code from a *registered* OTP device: <tap> # <- First OTP prompt here
Open your TOTP app and create a new manual entry with these fields:
Name: awly@localhost:3080
Issuer: Teleport
Algorithm: SHA1
Number of digits: 6
Period: 30s
Secret: 3UD42X2NN7EEZ6LUPG6NFMNOLDY6AJTS
Once created, enter an OTP code generated by the app: 607738 # <- Second OTP prompt here
MFA device "otp2" added.
```
Before this PR, the first OTP prompt (for `*registered* device`) would
hang in the background. The OTP code from the newly-registered device is
prompted later, but any text written ends up going to the first prompt.
After this PR, the first prompt is canceled and the code from a new
device goes to the second prompt as intended.
Note: this is implemented using pure Go code (background goroutine
consuming `os.Stdin`) rather than syscalls (e.g. `poll` or `select`)
for portability.
After adding several U2F tokens with `tsh mfa add`, you can now `tsh
login` using any of those tokens.
Two caveats:
1. The MFA method you get prompted for on login depends on the
`second_factor` config field on the auth server. There isn't yet an
option to require _either_ TOTP or U2F yet, even if you have both kinds
registered.
2. Web logins still need updating.
Also a few small unrelated changes:
- remove u2f-host binary presence check and docs
- hide `tsh mfa` commands until the feature is complete
* mfa: implement management commands in tsh
New commands are:
- tsh mfa ls
- tsh mfa add
- tsh mfa rm
There are 2 problems intentionally left in this PR to keep it small:
1. TOTP registration requires user to manually enter the secret in the
app. When there's free time, I'll add platform-specific QR code display
to make this easier.
2. U2F authentication only checks one of the registered devices. This is
a limitation of the u2f-host binary, which can't check multiple devices
at once (even if spawning multiple u2f-host commands in parallel). In
the next PR, I'll replace u2f-host with a Go library that supports this.
* Address review feedback
* When exporting a kubeconfig, optionally overwrite the target file
Running `tctl auth sign ---out=filepath` or `tsh login --out=filepath`
might overwrite the target `filepath` regardless of its existing
contents.
Make the tools prompt the user before overwriting (by default) and
provide a flag to always force the overwrite (for automation).
Without overwrites, writing kubeconfig could fail if parsing the
existing file as a kubeconfig fails.