mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-28 03:19:47 +08:00
@@ -0,0 +1,291 @@
|
||||
---
|
||||
authors: Andrew Lytvynov (andrew@goteleport.com)
|
||||
state: draft
|
||||
---
|
||||
|
||||
# RFD 15 - 2FA device management
|
||||
|
||||
## What
|
||||
|
||||
Improvements to 2FA device management (OTP and U2F):
|
||||
- support for multiple 2FA devices per account
|
||||
- ability to mix OTP and U2F devices (and possibly more in the future)
|
||||
- ability to list/add/delete devices by the user from Web UI and CLI
|
||||
|
||||
## Why
|
||||
|
||||
Currently, Teleport only supports a single OTP or U2F device per local user.
|
||||
This setting is global to the cluster - either everyone uses OTP or U2F or
|
||||
nothing. Users don't have a choice, and losing a 2FA device requires an admin
|
||||
to reset the account to recover.
|
||||
|
||||
[RFD 14](0014-session-2FA.md) adds support for 2FA challenges per-connection.
|
||||
2FA devices will become more widely used by Enterprise users, who would
|
||||
previously delegate this to their SSO provider.
|
||||
|
||||
Teleport needs to make 2FA device support more flexible, as both a
|
||||
long-standing OSS users' request, and the new Enterprise use-case.
|
||||
|
||||
## Details
|
||||
|
||||
### protocols
|
||||
|
||||
Teleport supports two 2FA protocols: OTP and U2F.
|
||||
|
||||
There's no change to the list of supported protocols, but the implementation
|
||||
should assume that more protocols will be added (e.g. WebAuthn).
|
||||
|
||||
For U2F, we will migrate from https://github.com/tstranex/u2f to
|
||||
https://github.com/flynn/u2f, to allow client-side CLI authentication without
|
||||
the `u2f-host` dependency.
|
||||
|
||||
### UX
|
||||
|
||||
For the below examples, we assume that the user already has at least one 2FA
|
||||
device enrolled. The [bootstrap](#bootstrap) section covers registration of the
|
||||
first device.
|
||||
|
||||
#### CLI
|
||||
|
||||
Login:
|
||||
|
||||
```sh
|
||||
$ tsh login --login=non-sso-user
|
||||
Enter password for Teleport user non-sso-user: ...
|
||||
Tap your security key... <tap>
|
||||
|
||||
$ tsh login --login=non-sso-user --mfa=otp
|
||||
Enter password for Teleport user non-sso-user: ...
|
||||
Enter OTP code: ...
|
||||
|
||||
$ tsh login --login=sso-user --auth=oidc
|
||||
# SSO page opens
|
||||
# no 2FA prompt from Teleport
|
||||
```
|
||||
|
||||
2FA management:
|
||||
|
||||
```sh
|
||||
$ tsh mfa ls
|
||||
MFA deivice name Type Added at Last used
|
||||
---------------- ---- ------------------------------- -------------------------------
|
||||
android OTP OTP Tue 08 Dec 2020 01:29:42 PM PST Tue 15 Dec 2020 01:29:42 PM PST
|
||||
yubikey U2F Wed 09 Dec 2020 02:00:13 PM PST Wed 16 Dec 2020 02:00:13 PM PST
|
||||
|
||||
# Add U2F token.
|
||||
$ tsh mfa add
|
||||
Adding a new MFA device.
|
||||
Choose device type (1 - OTP, 2 - U2F): 2
|
||||
Enter device name: solokey
|
||||
Tap any *registered* security key or enter an OTP code: <tap>
|
||||
Tap your *new* security key... <tap>
|
||||
MFA device "solokey" added.
|
||||
|
||||
# Add OTP app.
|
||||
$ tsh mfa add
|
||||
Adding a new MFA device.
|
||||
Choose device type (1 - OTP, 2 - U2F): 1
|
||||
Enter device name: my-otp-app
|
||||
Tap any *registered* security key or enter an OTP code: <tap>
|
||||
Enter this secret into your OTP app: 1234567890ABCDEF
|
||||
Enter the OTP code generated by your OTP app: 123456
|
||||
MFA device "my-otp-app" added.
|
||||
|
||||
$ tsh mfa ls
|
||||
MFA deivice name Type Added at Last used
|
||||
---------------- ---- ------------------------------- -------------------------------
|
||||
android OTP OTP Tue 08 Dec 2020 01:29:42 PM PST Tue 15 Dec 2020 01:29:42 PM PST
|
||||
yubikey U2F Wed 09 Dec 2020 02:00:13 PM PST Wed 16 Dec 2020 02:00:13 PM PST
|
||||
solokey U2F Wed 16 Dec 2020 02:05:46 PM PST Wed 16 Dec 2020 02:05:46 PM PST
|
||||
my-otp-app OTP Wed 16 Dec 2020 02:06:46 PM PST Wed 16 Dec 2020 02:06:46 PM PST
|
||||
|
||||
# remove by name
|
||||
$ tsh mfa rm yubikey
|
||||
Tap any *registered* security key or enter an OTP code: <tap>
|
||||
MFA device "yubikey" removed.
|
||||
|
||||
# remove by ID, which can be found via `tsh mfa ls -v`
|
||||
$ tsh mfa rm fa004bf4-acc7-435d-8965-5f5a0a4552e8
|
||||
Tap any *registered* security key or enter an OTP code: <tap>
|
||||
MFA device "android OTP" removed.
|
||||
|
||||
$ tsh mfa ls
|
||||
MFA deivice name Type Added at Last used
|
||||
---------------- ---- ------------------------------- -------------------------------
|
||||
solokey U2F Wed 16 Dec 2020 02:05:46 PM PST Wed 16 Dec 2020 02:05:46 PM PST
|
||||
my-otp-app OTP Wed 16 Dec 2020 02:06:46 PM PST Wed 16 Dec 2020 02:06:46 PM PST
|
||||
|
||||
# If 2FA is optional:
|
||||
$ tsh mfa rm solokey
|
||||
You are about to remove the only remaining MFA device.
|
||||
This will disable MFA during login.
|
||||
Are you sure? (y/N): N
|
||||
|
||||
# If 2FA is required:
|
||||
$ tsh mfa rm solokey
|
||||
Can't remove the only remaining MFA device.
|
||||
Please add a replacement MFA device first using "tsh mfa add".
|
||||
```
|
||||
|
||||
#### Web UI
|
||||
``
|
||||
|
||||
Web UI management of 2FA devices should be logically similar to the CLI:
|
||||
- a page to see all enrolled devices
|
||||
- a button to enroll a new device
|
||||
- buttons to remove any enrolled device (except for the last one)
|
||||
|
||||
Web UI details, wireframes and implementation will be added later, when we have
|
||||
the capacity to do it. Initially, 2FA management is CLI-only.
|
||||
|
||||
#### Bootstrap
|
||||
|
||||
Initially, a user account doesn't have a 2FA device. Depending on [cluster
|
||||
configuration](#configuration), use of 2FA devices might be required.
|
||||
|
||||
If 2FA is required, a user is required to enroll a device during account
|
||||
creation (for local users) or first login (for SSO users).
|
||||
|
||||
If 2FA is optional, a user can create an account and login without 2FA. They
|
||||
can then add 2FA devices as described above. If an existing user has at least
|
||||
one 2FA device registered, it's required during login.
|
||||
|
||||
### Configuration
|
||||
|
||||
The current 2FA configuration in Teleport only applies to local users and is
|
||||
always required. We need to allow this to be optional (for users to migrate)
|
||||
and allow to mix different 2FA methods.
|
||||
|
||||
Existing configuration options must be backwards-compatible - no change in
|
||||
behavior unless config values are changed.
|
||||
|
||||
New values for `auth_service.authentication.second_factor` for this:
|
||||
- `off` (existing) - no 2FA can be enrolled
|
||||
- `otp` (existing) - only OTP can be enrolled and is required for all local
|
||||
users
|
||||
- `u2f` (existing) - only U2F can be enrolled and is required for all local
|
||||
users
|
||||
- `on` (new) - users can enroll both OTP and U2F devices, and 2FA is required
|
||||
for all local users
|
||||
- `optional` (new) - users can enroll both OTP and U2F devices, and 2FA is
|
||||
required only for users with 2FA enrolled
|
||||
|
||||
#### Restricted device vendors
|
||||
|
||||
Another new option is restrictions on U2F device manufacturers. This is done
|
||||
using attestation certificates presented by the device during enrollment. See
|
||||
[FIDO
|
||||
docs](https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-overview-v1.2-ps-20170411.html#verifying-that-a-u2f-device-is-genuine)
|
||||
about attestation.
|
||||
|
||||
On the Teleport side, users can pass the trusted attestation CAs as so:
|
||||
|
||||
```yaml
|
||||
# teleport.yaml
|
||||
auth_service:
|
||||
authentication:
|
||||
second_factor: "on" # or "u2f" or "optional"
|
||||
u2f_device_attestation_cas:
|
||||
# CAs can be passed as local file paths
|
||||
- "/var/lib/teleport/u2f_ca1.pem"
|
||||
# or as raw PEM blocks
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1
|
||||
YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY
|
||||
DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg
|
||||
U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2
|
||||
cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E
|
||||
ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq
|
||||
joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH
|
||||
BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf
|
||||
wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet
|
||||
X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0
|
||||
1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s
|
||||
XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2
|
||||
lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d
|
||||
bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq
|
||||
Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8
|
||||
SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
For example, to restrict users to [only use
|
||||
Yubikeys](https://developers.yubico.com/U2F/Attestation_and_Metadata/), you can
|
||||
download their [attestation
|
||||
CA](https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt), write it into a
|
||||
file and set `u2f_device_attestation_cas: ["/path/to/yubikey_ca.pem"]` and
|
||||
restart the auth server. After this, users will only be able to enroll Yubikey
|
||||
devices.
|
||||
|
||||
Note that existing enrolled keys will remain, even if they aren't Yubikeys.
|
||||
If we wanted to re-check the existing enrolled keys against updated attestation
|
||||
CAs, we would have to store the attestation cert of every enrolled key. This
|
||||
would impose extra storage costs, add complexity and create the risk of user
|
||||
lockout.
|
||||
|
||||
### Backend storage
|
||||
|
||||
Each Teleport `User` object has a `LocalAuth` proto field:
|
||||
|
||||
```
|
||||
message LocalAuthSecrets {
|
||||
bytes PasswordHash = 1 [ (gogoproto.jsontag) = "password_hash,omitempty" ];
|
||||
string TOTPKey = 2 [ (gogoproto.jsontag) = "totp_key,omitempty" ];
|
||||
U2FRegistrationData U2FRegistration = 3 [ (gogoproto.jsontag) = "u2f_registration,omitempty" ];
|
||||
uint32 U2FCounter = 4 [ (gogoproto.jsontag) = "u2f_counter,omitempty" ];
|
||||
}
|
||||
```
|
||||
|
||||
To support multiple 2FA devices, we'll modify it:
|
||||
|
||||
```
|
||||
message LocalAuthSecrets {
|
||||
bytes PasswordHash = 1 [ (gogoproto.jsontag) = "password_hash,omitempty" ];
|
||||
|
||||
// Deprecated MFA fields.
|
||||
string TOTPKey = 2 [ deprecated = true, (gogoproto.jsontag) = "totp_key,omitempty" ];
|
||||
U2FRegistrationData U2FRegistration = 3 [ deprecated = true, (gogoproto.jsontag) = "u2f_registration,omitempty" ];
|
||||
uint32 U2FCounter = 4 [ deprecated = true, (gogoproto.jsontag) = "u2f_counter,omitempty" ];
|
||||
|
||||
repeated MFADevice MFA = 5;
|
||||
}
|
||||
|
||||
message MFADevice {
|
||||
string ID = 1;
|
||||
string Name = 2;
|
||||
google.protobuf.Timestamp AddedAt = 3;
|
||||
google.protobuf.Timestamp LastUsed = 4;
|
||||
oneof Device {
|
||||
TOTPDevice TOTP = 5;
|
||||
U2FDevice U2F = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message TOTPDevice {
|
||||
string Key = 1;
|
||||
}
|
||||
|
||||
message U2FDevice {
|
||||
// Copied from U2FRegistrationData
|
||||
bytes Raw = 1;
|
||||
bytes KeyHandle = 2;
|
||||
bytes PubKey = 3;
|
||||
|
||||
uint32 U2FCounter = 4;
|
||||
}
|
||||
```
|
||||
|
||||
#### Migration
|
||||
|
||||
The above `LocalAuthSecrets` will be migrated by Teleport at startup in v6.
|
||||
In v7, we will remove the deprecated MFA fields from `LocalAuthSecrets.`
|
||||
|
||||
### Audit log
|
||||
|
||||
All 2FA device operations (create/delete) will emit an audit log entry. The
|
||||
entry should contain the user, device UUID and device name at a minimum.
|
||||
|
||||
Logging in with a 2FA check will add a `With2FA` field containing the device
|
||||
UUID to `UserLogin` event.
|
||||
Reference in New Issue
Block a user