mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
Alias "u2f" to "webauthn" and partially cleanup (#10466)
Alias the "u2f" second factor mode to "webauthn", effectively sunsetting U2F in favor of WebAuthn. The change effectively disables "U2F mode" server-side, making Teleport use WebAuthn instead. This is in line with our compatibility promise, as Teleport 8.x clients are already WebAuthn-capable (and thus have no problems talking to the cluster). I have cleaned up a good chunk of U2F references in lib/web and lib/client, plus a few other places. Changes on lib/auth are just the necessary to get the tests back to good standing. There is more work to be done, but this seems enough for a single PR. #10375 * Remove "Disabled" field from types.Webauthn * Update generated protos * Treat second_factor "u2f" as "webauthn" * Remove references to Webauthn.Disabled * Remove U2F from lib/web/ * Remove U2F from lib/client/ * Remove U2F from lib/auth/ (partially) * Fix issues after rebase on master * Fix typo
This commit is contained in:
parent
5f1eb44af5
commit
f8b7b330ad
|
@ -134,9 +134,10 @@ const (
|
|||
SecondFactorOTP = SecondFactorType("otp")
|
||||
// SecondFactorU2F means that only U2F is supported for 2FA and 2FA is
|
||||
// required for all users.
|
||||
// U2F is marked for removal. It currently works as an alias for "webauthn".
|
||||
SecondFactorU2F = SecondFactorType("u2f")
|
||||
// SecondFactorWebauthn means that only Webauthn is supported for 2FA and 2FA is
|
||||
// required for all users.
|
||||
// SecondFactorWebauthn means that only Webauthn is supported for 2FA and 2FA
|
||||
// is required for all users.
|
||||
SecondFactorWebauthn = SecondFactorType("webauthn")
|
||||
// SecondFactorOn means that all 2FA protocols are supported and 2FA is
|
||||
// required for all users.
|
||||
|
|
|
@ -233,23 +233,17 @@ func (c *AuthPreferenceV2) GetPreferredLocalMFA() constants.SecondFactorType {
|
|||
switch sf := c.GetSecondFactor(); sf {
|
||||
case constants.SecondFactorOff:
|
||||
return "" // Nothing to suggest.
|
||||
case constants.SecondFactorOTP, constants.SecondFactorU2F, constants.SecondFactorWebauthn:
|
||||
return sf // If using a single method, then that is what it should be.
|
||||
case constants.SecondFactorOTP:
|
||||
return sf // Single method.
|
||||
case constants.SecondFactorU2F, constants.SecondFactorWebauthn:
|
||||
return constants.SecondFactorWebauthn // Always WebAuthn.
|
||||
case constants.SecondFactorOn, constants.SecondFactorOptional:
|
||||
// In order of preference:
|
||||
// 1. WebAuthn (public-key based)
|
||||
// 2. U2F (public-key based, deprecated by WebAuthn)
|
||||
// 3. OTP
|
||||
//
|
||||
// Presently, some configurations here are impossible to reach (U2F is
|
||||
// always required and WebAuthn always exists as a consequence).
|
||||
// Nevertheless, we make an effort to gracefully handle those situations.
|
||||
if w, err := c.GetWebauthn(); err == nil && !w.Disabled {
|
||||
// 2. OTP
|
||||
if _, err := c.GetWebauthn(); err == nil {
|
||||
return constants.SecondFactorWebauthn
|
||||
}
|
||||
if _, err := c.GetU2F(); err == nil {
|
||||
return constants.SecondFactorU2F
|
||||
}
|
||||
return constants.SecondFactorOTP
|
||||
default:
|
||||
log.Warnf("Unexpected second_factor setting: %v", sf)
|
||||
|
@ -271,37 +265,24 @@ func (c *AuthPreferenceV2) IsSecondFactorTOTPAllowed() bool {
|
|||
|
||||
// IsSecondFactorU2FAllowed checks if users are allowed to register U2F devices.
|
||||
func (c *AuthPreferenceV2) IsSecondFactorU2FAllowed() bool {
|
||||
// Is U2F configured?
|
||||
switch _, err := c.GetU2F(); {
|
||||
case trace.IsNotFound(err): // OK, expected to happen in some cases.
|
||||
return false
|
||||
case err != nil:
|
||||
log.WithError(err).Warnf("Got unexpected error when reading U2F config")
|
||||
return false
|
||||
}
|
||||
|
||||
// Are second factor settings in accordance?
|
||||
return c.Spec.SecondFactor == constants.SecondFactorU2F ||
|
||||
c.Spec.SecondFactor == constants.SecondFactorOptional ||
|
||||
c.Spec.SecondFactor == constants.SecondFactorOn
|
||||
return false // Never allowed, marked for removal.
|
||||
}
|
||||
|
||||
// IsSecondFactorWebauthnAllowed checks if users are allowed to register
|
||||
// Webauthn devices.
|
||||
func (c *AuthPreferenceV2) IsSecondFactorWebauthnAllowed() bool {
|
||||
// Is Webauthn configured and enabled?
|
||||
switch webConfig, err := c.GetWebauthn(); {
|
||||
switch _, err := c.GetWebauthn(); {
|
||||
case trace.IsNotFound(err): // OK, expected to happen in some cases.
|
||||
return false
|
||||
case err != nil:
|
||||
log.WithError(err).Warnf("Got unexpected error when reading Webauthn config")
|
||||
return false
|
||||
case webConfig.Disabled: // OK, fallback to U2F in use.
|
||||
return false
|
||||
}
|
||||
|
||||
// Are second factor settings in accordance?
|
||||
return c.Spec.SecondFactor == constants.SecondFactorWebauthn ||
|
||||
return c.Spec.SecondFactor == constants.SecondFactorU2F ||
|
||||
c.Spec.SecondFactor == constants.SecondFactorWebauthn ||
|
||||
c.Spec.SecondFactor == constants.SecondFactorOptional ||
|
||||
c.Spec.SecondFactor == constants.SecondFactorOn
|
||||
}
|
||||
|
@ -428,16 +409,18 @@ func (c *AuthPreferenceV2) CheckAndSetDefaults() error {
|
|||
return trace.BadParameter("authentication type %q not supported", c.Spec.Type)
|
||||
}
|
||||
|
||||
// DELETE IN 11.0, time to sunset U2F (codingllama).
|
||||
if c.Spec.SecondFactor == constants.SecondFactorU2F {
|
||||
log.Warnf(`` +
|
||||
`Second Factor "u2f" is deprecated and marked for removal, using "webauthn" instead. ` +
|
||||
`Please update your configuration to use WebAuthn. ` +
|
||||
`Refer to https://goteleport.com/docs/access-controls/guides/webauthn/`)
|
||||
c.Spec.SecondFactor = constants.SecondFactorWebauthn
|
||||
}
|
||||
|
||||
// make sure second factor makes sense
|
||||
switch sf := c.Spec.SecondFactor; sf {
|
||||
case constants.SecondFactorOff, constants.SecondFactorOTP:
|
||||
case constants.SecondFactorU2F:
|
||||
if c.Spec.U2F == nil {
|
||||
return trace.BadParameter("missing required U2F configuration for second factor type %q", sf)
|
||||
}
|
||||
if err := c.Spec.U2F.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
case constants.SecondFactorWebauthn:
|
||||
// If U2F is present validate it, we can derive Webauthn from it.
|
||||
if c.Spec.U2F != nil {
|
||||
|
@ -449,11 +432,8 @@ func (c *AuthPreferenceV2) CheckAndSetDefaults() error {
|
|||
c.Spec.Webauthn = &Webauthn{}
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case c.Spec.Webauthn == nil:
|
||||
if c.Spec.Webauthn == nil {
|
||||
return trace.BadParameter("missing required webauthn configuration for second factor type %q", sf)
|
||||
case c.Spec.Webauthn.Disabled:
|
||||
return trace.BadParameter("disabled webauthn configuration not allowed for second factor type %q", sf)
|
||||
}
|
||||
if err := c.Spec.Webauthn.CheckAndSetDefaults(c.Spec.U2F); err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -462,13 +442,9 @@ func (c *AuthPreferenceV2) CheckAndSetDefaults() error {
|
|||
// The following scenarios are allowed for "on" and "optional":
|
||||
// - Webauthn is configured (preferred)
|
||||
// - U2F is configured, Webauthn derived from it (U2F-compat mode)
|
||||
// - U2F is configured, Webauthn is disabled (fallback mode)
|
||||
|
||||
switch {
|
||||
case c.Spec.Webauthn == nil && c.Spec.U2F == nil:
|
||||
if c.Spec.U2F == nil && c.Spec.Webauthn == nil {
|
||||
return trace.BadParameter("missing required webauthn configuration for second factor type %q", sf)
|
||||
case c.Spec.Webauthn != nil && c.Spec.Webauthn.Disabled && c.Spec.U2F == nil:
|
||||
return trace.BadParameter("missing u2f configuration with disabled webauthn not allowed for second factor %q", sf)
|
||||
}
|
||||
|
||||
// Is U2F configured?
|
||||
|
|
|
@ -75,6 +75,7 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
constants.SecondFactorOptional,
|
||||
}
|
||||
secondFactorWebActive := []constants.SecondFactorType{
|
||||
constants.SecondFactorU2F,
|
||||
constants.SecondFactorWebauthn,
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
|
@ -121,11 +122,16 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "NOK U2F missing U2F",
|
||||
name: "OK U2F aliased to Webauthn",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorU2F, // only mode where U2F is mandatory
|
||||
constants.SecondFactorU2F,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.Equal(t, constants.SecondFactorWebauthn, got.Spec.SecondFactor)
|
||||
},
|
||||
wantErr: "missing required U2F configuration",
|
||||
},
|
||||
// Webauthn tests.
|
||||
{
|
||||
|
@ -185,52 +191,6 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
require.Empty(t, cmp.Diff(wantWeb, gotWeb))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK Webauthn disabled with fallback",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOff,
|
||||
constants.SecondFactorOTP,
|
||||
constants.SecondFactorU2F,
|
||||
// constants.SecondFactorWebauthn excluded
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
Webauthn: &types.Webauthn{
|
||||
Disabled: true,
|
||||
},
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.False(t, got.IsSecondFactorWebauthnAllowed(), "webauthn second factor allowed")
|
||||
require.NotEqual(t, constants.SecondFactorWebauthn, got.GetPreferredLocalMFA(), "webauthn set as preferred MFA")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NOK Webauthn disabled in own mode",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorWebauthn,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
Webauthn: &types.Webauthn{
|
||||
Disabled: true,
|
||||
},
|
||||
},
|
||||
wantErr: "disabled webauthn configuration not allowed",
|
||||
},
|
||||
{
|
||||
name: "NOK Webauthn disabled without fallback",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
Webauthn: &types.Webauthn{
|
||||
Disabled: true,
|
||||
},
|
||||
},
|
||||
wantErr: "missing u2f configuration",
|
||||
},
|
||||
{
|
||||
name: "OK Webauthn with attestation CAs",
|
||||
secondFactors: secondFactorWebActive,
|
||||
|
@ -338,51 +298,26 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "OK U2F second factor allowed",
|
||||
name: "OK U2F second factor never allowed",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOff,
|
||||
constants.SecondFactorOTP,
|
||||
constants.SecondFactorU2F,
|
||||
constants.SecondFactorWebauthn,
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.True(t, got.IsSecondFactorU2FAllowed(), "U2F not allowed")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK U2F second factor not allowed",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOff,
|
||||
constants.SecondFactorOTP,
|
||||
constants.SecondFactorWebauthn,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
Webauthn: minimalWeb,
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.False(t, got.IsSecondFactorU2FAllowed(), "U2F allowed")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK U2F second factor not allowed when not configured",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
Webauthn: minimalWeb,
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.False(t, got.IsSecondFactorU2FAllowed(), "U2F allowed")
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "OK Webauthn second factor allowed",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorU2F,
|
||||
constants.SecondFactorWebauthn,
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
|
@ -399,7 +334,6 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOff,
|
||||
constants.SecondFactorOTP,
|
||||
constants.SecondFactorU2F,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
|
@ -409,22 +343,6 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
require.False(t, got.IsSecondFactorWebauthnAllowed(), "Webauthn allowed")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK Webauthn second factor not allowed when disabled",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
Webauthn: &types.Webauthn{
|
||||
Disabled: true,
|
||||
},
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.False(t, got.IsSecondFactorWebauthnAllowed(), "Webauthn allowed")
|
||||
},
|
||||
},
|
||||
// GetPreferredLocalMFA
|
||||
{
|
||||
name: "OK preferred local MFA empty",
|
||||
|
@ -444,26 +362,10 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
|
|||
require.Equal(t, constants.SecondFactorOTP, got.GetPreferredLocalMFA())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK preferred local MFA = U2F",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorU2F,
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
},
|
||||
spec: types.AuthPreferenceSpecV2{
|
||||
U2F: minimalU2F,
|
||||
Webauthn: &types.Webauthn{
|
||||
Disabled: true,
|
||||
},
|
||||
},
|
||||
assertFn: func(t *testing.T, got *types.AuthPreferenceV2) {
|
||||
require.Equal(t, constants.SecondFactorU2F, got.GetPreferredLocalMFA())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK preferred local MFA = Webauthn",
|
||||
secondFactors: []constants.SecondFactorType{
|
||||
constants.SecondFactorU2F,
|
||||
constants.SecondFactorWebauthn,
|
||||
constants.SecondFactorOn,
|
||||
constants.SecondFactorOptional,
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1083,14 +1083,7 @@ message Webauthn {
|
|||
// By default no devices are denied.
|
||||
repeated string AttestationDeniedCAs = 3
|
||||
[ (gogoproto.jsontag) = "attestation_denied_cas,omitempty" ];
|
||||
// Disables Webauthn, regardless of other cluster settings.
|
||||
// Allows fallback to pure U2F in clusters with second_factor:on or
|
||||
// second_factor:optional.
|
||||
// Must not be set for clusters with second_factor:webauthn.
|
||||
// Temporary safety switch for Webauthn, to be removed in future versions of
|
||||
// Teleport.
|
||||
// DELETE IN 9.x, fallback not possible without U2F (codingllama).
|
||||
bool Disabled = 4 [ (gogoproto.jsontag) = "disabled,omitempty" ];
|
||||
reserved 4; // bool Disabled
|
||||
}
|
||||
|
||||
// Namespace represents namespace resource specification
|
||||
|
|
|
@ -30,8 +30,6 @@ import (
|
|||
"github.com/gravitational/teleport/api/types"
|
||||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
wantypes "github.com/gravitational/teleport/api/types/webauthn"
|
||||
"github.com/gravitational/teleport/lib/auth/mocku2f"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/events"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
|
@ -682,21 +680,6 @@ func TestCompleteAccountRecovery(t *testing.T) {
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add new U2F device",
|
||||
getRequest: func() *proto.CompleteAccountRecoveryRequest {
|
||||
u2fRegResp, _, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), approvedToken.GetName())
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.CompleteAccountRecoveryRequest{
|
||||
NewDeviceName: "new-u2f",
|
||||
RecoveryApprovedTokenID: approvedToken.GetName(),
|
||||
NewAuthnCred: &proto.CompleteAccountRecoveryRequest_NewMFAResponse{NewMFAResponse: &proto.MFARegisterResponse{
|
||||
Response: &proto.MFARegisterResponse_U2F{U2F: u2fRegResp},
|
||||
}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add new WEBAUTHN device",
|
||||
getRequest: func() *proto.CompleteAccountRecoveryRequest {
|
||||
|
@ -827,16 +810,16 @@ func TestCompleteAccountRecovery_WithErrors(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NotEmpty(t, devs)
|
||||
|
||||
// New u2f register response.
|
||||
u2fRegResp, _, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), approvedToken.GetName())
|
||||
// New register response.
|
||||
_, mfaResp, err := getMockedWebauthnAndRegisterRes(srv.Auth(), approvedToken.GetName())
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.CompleteAccountRecoveryRequest{
|
||||
RecoveryApprovedTokenID: approvedToken.GetName(),
|
||||
NewDeviceName: devs[0].GetName(),
|
||||
NewAuthnCred: &proto.CompleteAccountRecoveryRequest_NewMFAResponse{NewMFAResponse: &proto.MFARegisterResponse{
|
||||
Response: &proto.MFARegisterResponse_U2F{U2F: u2fRegResp},
|
||||
}},
|
||||
NewAuthnCred: &proto.CompleteAccountRecoveryRequest_NewMFAResponse{
|
||||
NewMFAResponse: mfaResp,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -1015,35 +998,6 @@ func TestAccountRecoveryFlow(t *testing.T) {
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recover u2f with password",
|
||||
getStartRequest: func(u *userAuthCreds) *proto.StartAccountRecoveryRequest {
|
||||
return &proto.StartAccountRecoveryRequest{
|
||||
Username: u.username,
|
||||
RecoverType: types.UserTokenUsage_USER_TOKEN_RECOVER_MFA,
|
||||
RecoveryCode: []byte(u.recoveryCodes[0]),
|
||||
}
|
||||
},
|
||||
getApproveRequest: func(u *userAuthCreds, c *proto.MFAAuthenticateChallenge, startTokenID string) *proto.VerifyAccountRecoveryRequest {
|
||||
return &proto.VerifyAccountRecoveryRequest{
|
||||
RecoveryStartTokenID: startTokenID,
|
||||
Username: u.username,
|
||||
AuthnCred: &proto.VerifyAccountRecoveryRequest_Password{Password: u.password},
|
||||
}
|
||||
},
|
||||
getCompleteRequest: func(u *userAuthCreds, approvedTokenID string) *proto.CompleteAccountRecoveryRequest {
|
||||
u2fRegResp, _, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), approvedTokenID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.CompleteAccountRecoveryRequest{
|
||||
NewDeviceName: "new-u2f",
|
||||
RecoveryApprovedTokenID: approvedTokenID,
|
||||
NewAuthnCred: &proto.CompleteAccountRecoveryRequest_NewMFAResponse{NewMFAResponse: &proto.MFARegisterResponse{
|
||||
Response: &proto.MFARegisterResponse_U2F{U2F: u2fRegResp},
|
||||
}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recover otp with password",
|
||||
getStartRequest: func(u *userAuthCreds) *proto.StartAccountRecoveryRequest {
|
||||
|
@ -1370,9 +1324,8 @@ func createUserWithSecondFactors(srv *TestTLSServer) (*userAuthCreds, error) {
|
|||
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOn,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
},
|
||||
// Use default Webauthn config.
|
||||
})
|
||||
|
@ -1396,17 +1349,16 @@ func createUserWithSecondFactors(srv *TestTLSServer) (*userAuthCreds, error) {
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Insert a password, u2f device, and recovery codes.
|
||||
u2fRegResp, u2fKey, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), resetToken.GetName())
|
||||
// Insert a password, device, and recovery codes.
|
||||
webDev, mfaResp, err := getMockedWebauthnAndRegisterRes(srv.Auth(), resetToken.GetName())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
res, err := srv.Auth().ChangeUserAuthentication(ctx, &proto.ChangeUserAuthenticationRequest{
|
||||
TokenID: resetToken.GetName(),
|
||||
NewPassword: password,
|
||||
NewMFARegisterResponse: &proto.MFARegisterResponse{
|
||||
Response: &proto.MFARegisterResponse_U2F{U2F: u2fRegResp}},
|
||||
TokenID: resetToken.GetName(),
|
||||
NewPassword: password,
|
||||
NewMFARegisterResponse: mfaResp,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
@ -1417,12 +1369,7 @@ func createUserWithSecondFactors(srv *TestTLSServer) (*userAuthCreds, error) {
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
u2fDev := &TestDevice{Key: u2fKey}
|
||||
totpDev, err := RegisterTestDevice(ctx, clt, "otp-1", proto.DeviceType_DEVICE_TYPE_TOTP, u2fDev, WithTestDeviceClock(srv.Clock()))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
webDev, err := RegisterTestDevice(ctx, clt, "web-1", proto.DeviceType_DEVICE_TYPE_WEBAUTHN, u2fDev)
|
||||
totpDev, err := RegisterTestDevice(ctx, clt, "otp-1", proto.DeviceType_DEVICE_TYPE_TOTP, webDev, WithTestDeviceClock(srv.Clock()))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -1436,36 +1383,6 @@ func createUserWithSecondFactors(srv *TestTLSServer) (*userAuthCreds, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// DELETE IN 9.0.0 in favor of getMockedWebauthnAndRegisterRes.
|
||||
func getLegacyMockedU2FAndRegisterRes(authSrv *Server, tokenID string) (*proto.U2FRegisterResponse, *mocku2f.Key, error) {
|
||||
res, err := authSrv.CreateRegisterChallenge(context.Background(), &proto.CreateRegisterChallengeRequest{
|
||||
TokenID: tokenID,
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
u2fKey, err := mocku2f.Create()
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
u2fRegResp, err := u2fKey.RegisterResponse(&u2f.RegisterChallenge{
|
||||
Version: res.GetU2F().GetVersion(),
|
||||
Challenge: res.GetU2F().GetChallenge(),
|
||||
AppID: res.GetU2F().GetAppID(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return &proto.U2FRegisterResponse{
|
||||
RegistrationData: u2fRegResp.RegistrationData,
|
||||
ClientData: u2fRegResp.ClientData,
|
||||
}, u2fKey, nil
|
||||
}
|
||||
|
||||
func getMockedWebauthnAndRegisterRes(authSrv *Server, tokenID string) (*TestDevice, *proto.MFARegisterResponse, error) {
|
||||
res, err := authSrv.CreateRegisterChallenge(context.Background(), &proto.CreateRegisterChallengeRequest{
|
||||
TokenID: tokenID,
|
||||
|
|
|
@ -69,22 +69,6 @@ func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) {
|
|||
require.Empty(t, challenge.GetWebauthnChallenge())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK second_factor:u2f",
|
||||
spec: &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorU2F,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
},
|
||||
},
|
||||
assertChallenge: func(challenge *proto.MFAAuthenticateChallenge) {
|
||||
require.Empty(t, challenge.GetTOTP())
|
||||
require.NotEmpty(t, challenge.GetU2F())
|
||||
require.Empty(t, challenge.GetWebauthnChallenge())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK second_factor:webauthn (derived from U2F)",
|
||||
spec: &types.AuthPreferenceSpecV2{
|
||||
|
@ -144,34 +128,13 @@ func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) {
|
|||
spec: &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOptional,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
},
|
||||
},
|
||||
assertChallenge: func(challenge *proto.MFAAuthenticateChallenge) {
|
||||
require.NotNil(t, challenge.GetTOTP())
|
||||
require.NotEmpty(t, challenge.GetU2F())
|
||||
require.NotEmpty(t, challenge.GetWebauthnChallenge())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK second_factor:optional with global Webauthn disable",
|
||||
spec: &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOptional,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
},
|
||||
Webauthn: &types.Webauthn{
|
||||
Disabled: true,
|
||||
RPID: "localhost",
|
||||
},
|
||||
},
|
||||
assertChallenge: func(challenge *proto.MFAAuthenticateChallenge) {
|
||||
require.NotNil(t, challenge.GetTOTP())
|
||||
require.NotEmpty(t, challenge.GetU2F())
|
||||
require.Empty(t, challenge.GetWebauthnChallenge())
|
||||
require.NotEmpty(t, challenge.GetWebauthnChallenge())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -179,14 +142,12 @@ func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) {
|
|||
spec: &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOn,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
},
|
||||
},
|
||||
assertChallenge: func(challenge *proto.MFAAuthenticateChallenge) {
|
||||
require.NotNil(t, challenge.GetTOTP())
|
||||
require.NotEmpty(t, challenge.GetU2F())
|
||||
require.NotEmpty(t, challenge.GetWebauthnChallenge())
|
||||
},
|
||||
},
|
||||
|
@ -198,10 +159,11 @@ func TestServer_CreateAuthenticateChallenge_authPreference(t *testing.T) {
|
|||
require.NoError(t, authServer.SetAuthPreference(ctx, authPreference))
|
||||
|
||||
challenge, err := authServer.CreateAuthenticateChallenge(ctx, &proto.CreateAuthenticateChallengeRequest{
|
||||
Request: &proto.CreateAuthenticateChallengeRequest_UserCredentials{UserCredentials: &proto.UserCredentials{
|
||||
Username: username,
|
||||
Password: []byte(password),
|
||||
}},
|
||||
Request: &proto.CreateAuthenticateChallengeRequest_UserCredentials{
|
||||
UserCredentials: &proto.UserCredentials{
|
||||
Username: username,
|
||||
Password: []byte(password),
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
test.assertChallenge(challenge)
|
||||
|
@ -233,7 +195,6 @@ func TestServer_AuthenticateUser_mfaDevices(t *testing.T) {
|
|||
solveChallenge func(*proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error)
|
||||
}{
|
||||
{name: "OK TOTP device", solveChallenge: mfa.TOTPDev.SolveAuthn},
|
||||
{name: "OK U2F device", solveChallenge: mfa.U2FDev.SolveAuthn},
|
||||
{name: "OK Webauthn device", solveChallenge: mfa.WebDev.SolveAuthn},
|
||||
}
|
||||
for _, test := range tests {
|
||||
|
@ -371,7 +332,6 @@ func TestCreateAuthenticateChallenge_WithUserCredentials(t *testing.T) {
|
|||
default:
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res.GetTOTP())
|
||||
require.NotEmpty(t, res.GetU2F())
|
||||
require.NotEmpty(t, res.GetWebauthnChallenge())
|
||||
}
|
||||
})
|
||||
|
@ -463,7 +423,6 @@ func TestCreateAuthenticateChallenge_WithRecoveryStartToken(t *testing.T) {
|
|||
default:
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, res.GetTOTP())
|
||||
require.NotEmpty(t, res.GetU2F())
|
||||
require.NotEmpty(t, res.GetWebauthnChallenge())
|
||||
}
|
||||
})
|
||||
|
@ -502,10 +461,6 @@ func TestCreateRegisterChallenge(t *testing.T) {
|
|||
wantErr bool
|
||||
deviceType proto.DeviceType
|
||||
}{
|
||||
{
|
||||
name: "u2f challenge",
|
||||
deviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
},
|
||||
{
|
||||
name: "totp challenge",
|
||||
deviceType: proto.DeviceType_DEVICE_TYPE_TOTP,
|
||||
|
@ -529,10 +484,6 @@ func TestCreateRegisterChallenge(t *testing.T) {
|
|||
switch tc.deviceType {
|
||||
case proto.DeviceType_DEVICE_TYPE_TOTP:
|
||||
require.NotNil(t, res.GetTOTP().GetQRCode())
|
||||
|
||||
case proto.DeviceType_DEVICE_TYPE_U2F:
|
||||
require.NotNil(t, res.GetU2F())
|
||||
|
||||
case proto.DeviceType_DEVICE_TYPE_WEBAUTHN:
|
||||
require.NotNil(t, res.GetWebauthn())
|
||||
}
|
||||
|
@ -541,17 +492,16 @@ func TestCreateRegisterChallenge(t *testing.T) {
|
|||
}
|
||||
|
||||
type configureMFAResp struct {
|
||||
User, Password string
|
||||
TOTPDev, U2FDev, WebDev *TestDevice
|
||||
User, Password string
|
||||
TOTPDev, WebDev *TestDevice
|
||||
}
|
||||
|
||||
func configureForMFA(t *testing.T, srv *TestTLSServer) *configureMFAResp {
|
||||
authPreference, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOptional,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
},
|
||||
// Use default Webauthn config.
|
||||
})
|
||||
|
@ -574,9 +524,6 @@ func configureForMFA(t *testing.T, srv *TestTLSServer) *configureMFAResp {
|
|||
totpDev, err := RegisterTestDevice(ctx, clt, "totp-1", proto.DeviceType_DEVICE_TYPE_TOTP, nil, WithTestDeviceClock(srv.Clock()))
|
||||
require.NoError(t, err)
|
||||
|
||||
u2fDev, err := RegisterTestDevice(ctx, clt, "u2f-1", proto.DeviceType_DEVICE_TYPE_U2F, totpDev)
|
||||
require.NoError(t, err)
|
||||
|
||||
webDev, err := RegisterTestDevice(ctx, clt, "web-1", proto.DeviceType_DEVICE_TYPE_WEBAUTHN, totpDev)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -584,7 +531,6 @@ func configureForMFA(t *testing.T, srv *TestTLSServer) *configureMFAResp {
|
|||
User: username,
|
||||
Password: password,
|
||||
TOTPDev: totpDev,
|
||||
U2FDev: u2fDev,
|
||||
WebDev: webDev,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1591,27 +1591,6 @@ func TestAddMFADeviceSync(t *testing.T) {
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "U2F device with privilege exception token",
|
||||
deviceName: "new-u2f",
|
||||
getReq: func(deviceName string) *proto.AddMFADeviceSyncRequest {
|
||||
// Obtain a privilege exception token.
|
||||
privExToken, err := srv.Auth().createPrivilegeToken(ctx, u.username, UserTokenTypePrivilegeException)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create register challenge and sign.
|
||||
u2fRegRes, _, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), privExToken.GetName())
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.AddMFADeviceSyncRequest{
|
||||
TokenID: privExToken.GetName(),
|
||||
NewDeviceName: deviceName,
|
||||
NewMFAResponse: &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: u2fRegRes,
|
||||
}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Webauthn device with privilege exception token",
|
||||
deviceName: "new-webauthn",
|
||||
|
|
|
@ -19,7 +19,6 @@ package auth
|
|||
import (
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
|
@ -38,7 +37,6 @@ import (
|
|||
apiutils "github.com/gravitational/teleport/api/utils"
|
||||
"github.com/gravitational/teleport/api/utils/sshutils"
|
||||
"github.com/gravitational/teleport/lib/auth/mocku2f"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
|
@ -98,39 +96,6 @@ func TestMFADeviceManagement(t *testing.T) {
|
|||
desc string
|
||||
opts mfaAddTestOpts
|
||||
}{
|
||||
{
|
||||
desc: "fail U2F auth challenge",
|
||||
opts: mfaAddTestOpts{
|
||||
initReq: &proto.AddMFADeviceRequestInit{
|
||||
DeviceName: "fail-dev",
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
},
|
||||
authHandler: func(t *testing.T, req *proto.MFAAuthenticateChallenge) *proto.MFAAuthenticateResponse {
|
||||
require.Len(t, req.U2F, 1)
|
||||
chal := req.U2F[0]
|
||||
|
||||
// Use a different, unregistered device, which should fail
|
||||
// the authentication challenge.
|
||||
keyHandle, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(chal.KeyHandle)
|
||||
require.NoError(t, err)
|
||||
badDev, err := mocku2f.CreateWithKeyHandle(keyHandle)
|
||||
require.NoError(t, err)
|
||||
mresp, err := badDev.SignResponse(&u2f.AuthenticateChallenge{
|
||||
Challenge: chal.Challenge,
|
||||
KeyHandle: chal.KeyHandle,
|
||||
AppID: chal.AppID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_U2F{U2F: &proto.U2FResponse{
|
||||
KeyHandle: mresp.KeyHandle,
|
||||
ClientData: mresp.ClientData,
|
||||
Signature: mresp.SignatureData,
|
||||
}}}
|
||||
},
|
||||
checkAuthErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fail TOTP auth challenge",
|
||||
opts: mfaAddTestOpts{
|
||||
|
@ -155,35 +120,6 @@ func TestMFADeviceManagement(t *testing.T) {
|
|||
checkAuthErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fail a U2F registration challenge",
|
||||
opts: mfaAddTestOpts{
|
||||
initReq: &proto.AddMFADeviceRequestInit{
|
||||
DeviceName: "fail-dev",
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
},
|
||||
authHandler: devs.u2fAuthHandler,
|
||||
checkAuthErr: require.NoError,
|
||||
registerHandler: func(t *testing.T, req *proto.MFARegisterChallenge) *proto.MFARegisterResponse {
|
||||
u2fRegisterChallenge := req.GetU2F()
|
||||
require.NotEmpty(t, u2fRegisterChallenge)
|
||||
|
||||
mdev, err := mocku2f.Create()
|
||||
require.NoError(t, err)
|
||||
mresp, err := mdev.RegisterResponse(&u2f.RegisterChallenge{
|
||||
Challenge: u2fRegisterChallenge.Challenge,
|
||||
AppID: "wrong app ID", // This should cause registration to fail.
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_U2F{U2F: &proto.U2FRegisterResponse{
|
||||
RegistrationData: mresp.RegistrationData,
|
||||
ClientData: mresp.ClientData,
|
||||
}}}
|
||||
},
|
||||
checkRegisterErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fail a TOTP registration challenge",
|
||||
opts: mfaAddTestOpts{
|
||||
|
@ -314,7 +250,7 @@ func TestMFADeviceManagement(t *testing.T) {
|
|||
deviceIDs[dev.GetName()] = dev.Id
|
||||
}
|
||||
sort.Strings(deviceNames)
|
||||
require.Equal(t, deviceNames, []string{devs.TOTPName, devs.U2FName, devs.WebName, webDev2Name})
|
||||
require.Equal(t, deviceNames, []string{devs.TOTPName, devs.WebName, webDev2Name})
|
||||
|
||||
// Delete several of the MFA devices.
|
||||
deleteTests := []struct {
|
||||
|
@ -354,38 +290,6 @@ func TestMFADeviceManagement(t *testing.T) {
|
|||
checkErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fail a U2F auth challenge",
|
||||
opts: mfaDeleteTestOpts{
|
||||
initReq: &proto.DeleteMFADeviceRequestInit{
|
||||
DeviceName: devs.U2FName,
|
||||
},
|
||||
authHandler: func(t *testing.T, req *proto.MFAAuthenticateChallenge) *proto.MFAAuthenticateResponse {
|
||||
require.Len(t, req.U2F, 1)
|
||||
chal := req.U2F[0]
|
||||
|
||||
// Use a different, unregistered device, which should fail
|
||||
// the authentication challenge.
|
||||
keyHandle, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(chal.KeyHandle)
|
||||
require.NoError(t, err)
|
||||
badDev, err := mocku2f.CreateWithKeyHandle(keyHandle)
|
||||
require.NoError(t, err)
|
||||
mresp, err := badDev.SignResponse(&u2f.AuthenticateChallenge{
|
||||
Challenge: chal.Challenge,
|
||||
KeyHandle: chal.KeyHandle,
|
||||
AppID: chal.AppID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_U2F{U2F: &proto.U2FResponse{
|
||||
KeyHandle: mresp.KeyHandle,
|
||||
ClientData: mresp.ClientData,
|
||||
Signature: mresp.SignatureData,
|
||||
}}}
|
||||
},
|
||||
checkErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fail a webauthn auth challenge",
|
||||
opts: mfaDeleteTestOpts{
|
||||
|
@ -421,16 +325,6 @@ func TestMFADeviceManagement(t *testing.T) {
|
|||
checkErr: require.NoError,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "delete U2F device by ID",
|
||||
opts: mfaDeleteTestOpts{
|
||||
initReq: &proto.DeleteMFADeviceRequestInit{
|
||||
DeviceName: deviceIDs[devs.U2FName],
|
||||
},
|
||||
authHandler: devs.u2fAuthHandler,
|
||||
checkErr: require.NoError,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "delete webauthn device by name",
|
||||
opts: mfaDeleteTestOpts{
|
||||
|
@ -478,8 +372,6 @@ type mfaDevices struct {
|
|||
webOrigin string
|
||||
|
||||
TOTPName, TOTPSecret string
|
||||
U2FName string
|
||||
U2FKey *mocku2f.Key
|
||||
WebName string
|
||||
WebKey *mocku2f.Key
|
||||
}
|
||||
|
@ -501,28 +393,6 @@ func (d *mfaDevices) totpAuthHandler(t *testing.T, challenge *proto.MFAAuthentic
|
|||
}
|
||||
}
|
||||
|
||||
func (d *mfaDevices) u2fAuthHandler(t *testing.T, challenge *proto.MFAAuthenticateChallenge) *proto.MFAAuthenticateResponse {
|
||||
require.Len(t, challenge.U2F, 1)
|
||||
c := challenge.U2F[0]
|
||||
|
||||
resp, err := d.U2FKey.SignResponse(&u2f.AuthenticateChallenge{
|
||||
Challenge: c.Challenge,
|
||||
KeyHandle: c.KeyHandle,
|
||||
AppID: c.AppID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.MFAAuthenticateResponse{
|
||||
Response: &proto.MFAAuthenticateResponse_U2F{
|
||||
U2F: &proto.U2FResponse{
|
||||
KeyHandle: resp.KeyHandle,
|
||||
ClientData: resp.ClientData,
|
||||
Signature: resp.SignatureData,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *mfaDevices) webAuthHandler(t *testing.T, challenge *proto.MFAAuthenticateChallenge) *proto.MFAAuthenticateResponse {
|
||||
require.NotNil(t, challenge.WebauthnChallenge)
|
||||
|
||||
|
@ -538,19 +408,15 @@ func (d *mfaDevices) webAuthHandler(t *testing.T, challenge *proto.MFAAuthentica
|
|||
|
||||
func addOneOfEachMFADevice(t *testing.T, cl *Client, clock clockwork.Clock, origin string) mfaDevices {
|
||||
const totpName = "totp-dev"
|
||||
const u2fName = "u2f-dev"
|
||||
const webName = "webauthn-dev"
|
||||
devs := mfaDevices{
|
||||
clock: clock,
|
||||
webOrigin: origin,
|
||||
TOTPName: totpName,
|
||||
U2FName: u2fName,
|
||||
WebName: webName,
|
||||
}
|
||||
|
||||
var err error
|
||||
devs.U2FKey, err = mocku2f.Create()
|
||||
require.NoError(t, err)
|
||||
devs.WebKey, err = mocku2f.Create()
|
||||
require.NoError(t, err)
|
||||
devs.WebKey.PreferRPID = true
|
||||
|
@ -602,49 +468,6 @@ func addOneOfEachMFADevice(t *testing.T, cl *Client, clock clockwork.Clock, orig
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "U2F device",
|
||||
opts: mfaAddTestOpts{
|
||||
initReq: &proto.AddMFADeviceRequestInit{
|
||||
DeviceName: u2fName,
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
},
|
||||
authHandler: devs.totpAuthHandler,
|
||||
checkAuthErr: require.NoError,
|
||||
registerHandler: func(t *testing.T, challenge *proto.MFARegisterChallenge) *proto.MFARegisterResponse {
|
||||
require.NotEmpty(t, challenge.GetU2F())
|
||||
|
||||
resp, err := devs.U2FKey.RegisterResponse(&u2f.RegisterChallenge{
|
||||
Challenge: challenge.GetU2F().Challenge,
|
||||
AppID: challenge.GetU2F().AppID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.MFARegisterResponse{
|
||||
Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: &proto.U2FRegisterResponse{
|
||||
RegistrationData: resp.RegistrationData,
|
||||
ClientData: resp.ClientData,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
checkRegisterErr: require.NoError,
|
||||
assertRegisteredDev: func(t *testing.T, got *types.MFADevice) {
|
||||
want, err := u2f.NewDevice(
|
||||
u2fName,
|
||||
&u2f.Registration{
|
||||
KeyHandle: devs.U2FKey.KeyHandle,
|
||||
PubKey: devs.U2FKey.PrivateKey.PublicKey,
|
||||
},
|
||||
clock.Now(),
|
||||
)
|
||||
want.Id = got.Id
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(want, got))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Webauthn device",
|
||||
opts: mfaAddTestOpts{
|
||||
|
@ -798,17 +621,6 @@ func TestDeleteLastMFADevice(t *testing.T) {
|
|||
checkErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NOK sf=U2F trying to delete last U2F device",
|
||||
secondFactor: constants.SecondFactorU2F,
|
||||
opts: mfaDeleteTestOpts{
|
||||
initReq: &proto.DeleteMFADeviceRequestInit{
|
||||
DeviceName: devs.U2FName,
|
||||
},
|
||||
authHandler: devs.u2fAuthHandler,
|
||||
checkErr: require.Error,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NOK sf=Webauthn trying to delete last Webauthn device",
|
||||
secondFactor: constants.SecondFactorWebauthn,
|
||||
|
@ -831,17 +643,6 @@ func TestDeleteLastMFADevice(t *testing.T) {
|
|||
checkErr: require.NoError,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK delete U2F device",
|
||||
secondFactor: constants.SecondFactorOn,
|
||||
opts: mfaDeleteTestOpts{
|
||||
initReq: &proto.DeleteMFADeviceRequestInit{
|
||||
DeviceName: devs.U2FName,
|
||||
},
|
||||
authHandler: devs.u2fAuthHandler,
|
||||
checkErr: require.NoError,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NOK sf=on trying to delete last MFA device",
|
||||
secondFactor: constants.SecondFactorOn,
|
||||
|
@ -960,13 +761,11 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
|
|||
// Fetch MFA device IDs.
|
||||
devs, err := srv.Auth().Identity.GetMFADevices(ctx, user.GetName(), false)
|
||||
require.NoError(t, err)
|
||||
var u2fDevID, webDevID string
|
||||
var webDevID string
|
||||
for _, dev := range devs {
|
||||
switch {
|
||||
case dev.GetU2F() != nil:
|
||||
u2fDevID = dev.Id
|
||||
case dev.GetWebauthn() != nil:
|
||||
if dev.GetWebauthn() != nil {
|
||||
webDevID = dev.Id
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -977,32 +776,6 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
|
|||
desc string
|
||||
opts generateUserSingleUseCertTestOpts
|
||||
}{
|
||||
{
|
||||
desc: "ssh using U2F",
|
||||
opts: generateUserSingleUseCertTestOpts{
|
||||
initReq: &proto.UserCertsRequest{
|
||||
PublicKey: pub,
|
||||
Username: user.GetName(),
|
||||
Expires: clock.Now().Add(teleport.UserSingleUseCertTTL),
|
||||
Usage: proto.UserCertsRequest_SSH,
|
||||
NodeName: "node-a",
|
||||
},
|
||||
checkInitErr: require.NoError,
|
||||
authHandler: registered.u2fAuthHandler,
|
||||
checkAuthErr: require.NoError,
|
||||
validateCert: func(t *testing.T, c *proto.SingleUseUserCert) {
|
||||
crt := c.GetSSH()
|
||||
require.NotEmpty(t, crt)
|
||||
|
||||
cert, err := sshutils.ParseCertificate(crt)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, cert.Extensions[teleport.CertExtensionMFAVerified], u2fDevID)
|
||||
require.True(t, net.ParseIP(cert.Extensions[teleport.CertExtensionClientIP]).IsLoopback())
|
||||
require.Equal(t, cert.ValidBefore, uint64(clock.Now().Add(teleport.UserSingleUseCertTTL).Unix()))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "ssh using webauthn",
|
||||
opts: generateUserSingleUseCertTestOpts{
|
||||
|
|
|
@ -249,11 +249,6 @@ func TestServer_ChangePassword(t *testing.T) {
|
|||
newPass: "llamasarecool11",
|
||||
device: mfa.TOTPDev,
|
||||
},
|
||||
{
|
||||
name: "OK U2F-based change",
|
||||
newPass: "llamasarecool12",
|
||||
device: mfa.U2FDev,
|
||||
},
|
||||
{
|
||||
name: "OK Webauthn-based change",
|
||||
newPass: "llamasarecool13",
|
||||
|
@ -377,42 +372,6 @@ func TestChangeUserAuthentication(t *testing.T) {
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with second factor u2f",
|
||||
setAuthPreference: func() {
|
||||
authPreference, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorU2F,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = srv.Auth().SetAuthPreference(ctx, authPreference)
|
||||
require.NoError(t, err)
|
||||
},
|
||||
getReq: func(resetTokenID string) *proto.ChangeUserAuthenticationRequest {
|
||||
u2fRegResp, _, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), resetTokenID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.ChangeUserAuthenticationRequest{
|
||||
TokenID: resetTokenID,
|
||||
NewPassword: []byte("password3"),
|
||||
NewMFARegisterResponse: &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: u2fRegResp,
|
||||
}},
|
||||
}
|
||||
},
|
||||
// Invalid totp fields when auth settings set to only u2f.
|
||||
getInvalidReq: func(resetTokenID string) *proto.ChangeUserAuthenticationRequest {
|
||||
return &proto.ChangeUserAuthenticationRequest{
|
||||
TokenID: resetTokenID,
|
||||
NewPassword: []byte("password3"),
|
||||
NewMFARegisterResponse: &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_TOTP{}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with second factor webauthn",
|
||||
setAuthPreference: func() {
|
||||
|
@ -485,9 +444,8 @@ func TestChangeUserAuthentication(t *testing.T) {
|
|||
authPreference, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOn,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -495,15 +453,13 @@ func TestChangeUserAuthentication(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
},
|
||||
getReq: func(resetTokenID string) *proto.ChangeUserAuthenticationRequest {
|
||||
u2fRegResp, _, err := getLegacyMockedU2FAndRegisterRes(srv.Auth(), resetTokenID)
|
||||
_, mfaResp, err := getMockedWebauthnAndRegisterRes(srv.Auth(), resetTokenID)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &proto.ChangeUserAuthenticationRequest{
|
||||
TokenID: resetTokenID,
|
||||
NewPassword: []byte("password4"),
|
||||
NewMFARegisterResponse: &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: u2fRegResp,
|
||||
}},
|
||||
TokenID: resetTokenID,
|
||||
NewPassword: []byte("password4"),
|
||||
NewMFARegisterResponse: mfaResp,
|
||||
}
|
||||
},
|
||||
// Empty register response, when auth settings requires second factors.
|
||||
|
|
|
@ -63,10 +63,7 @@ type loginFlow struct {
|
|||
}
|
||||
|
||||
func (f *loginFlow) begin(ctx context.Context, user string, passwordless bool) (*CredentialAssertion, error) {
|
||||
switch {
|
||||
case f.Webauthn.Disabled:
|
||||
return nil, trace.BadParameter("webauthn disabled")
|
||||
case user == "" && !passwordless:
|
||||
if user == "" && !passwordless {
|
||||
return nil, trace.BadParameter("user required")
|
||||
}
|
||||
|
||||
|
@ -170,8 +167,6 @@ func beginLogin(
|
|||
|
||||
func (f *loginFlow) finish(ctx context.Context, user string, resp *CredentialAssertionResponse, passwordless bool) (*types.MFADevice, string, error) {
|
||||
switch {
|
||||
case f.Webauthn.Disabled:
|
||||
return nil, "", trace.BadParameter("webauthn disabled")
|
||||
case user == "" && !passwordless:
|
||||
return nil, "", trace.BadParameter("user required")
|
||||
case resp == nil:
|
||||
|
|
|
@ -35,84 +35,6 @@ import (
|
|||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
)
|
||||
|
||||
func TestWebauthnGlobalDisable(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
const user = "llama"
|
||||
cfg := &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
Disabled: true,
|
||||
}
|
||||
identity := newFakeIdentity(user)
|
||||
loginFlow := &wanlib.LoginFlow{
|
||||
Webauthn: cfg,
|
||||
Identity: identity,
|
||||
}
|
||||
passwordlessFlow := &wanlib.PasswordlessFlow{
|
||||
Webauthn: cfg,
|
||||
Identity: identity,
|
||||
}
|
||||
registrationFlow := &wanlib.RegistrationFlow{
|
||||
Webauthn: cfg,
|
||||
Identity: identity,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func() error
|
||||
}{
|
||||
{
|
||||
name: "LoginFlow.Begin",
|
||||
fn: func() error {
|
||||
_, err := loginFlow.Begin(ctx, user)
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LoginFlow.Finish",
|
||||
fn: func() error {
|
||||
_, err := loginFlow.Finish(ctx, user, &wanlib.CredentialAssertionResponse{})
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PasswordlessFlow.Begin",
|
||||
fn: func() error {
|
||||
_, err := passwordlessFlow.Begin(ctx)
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PasswordlessFlow.Finish",
|
||||
fn: func() error {
|
||||
_, _, err := passwordlessFlow.Finish(ctx, &wanlib.CredentialAssertionResponse{})
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "RegistrationFlow.Begin",
|
||||
fn: func() error {
|
||||
_, err := registrationFlow.Begin(ctx, user, false /* passwordless */)
|
||||
return err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "RegistrationFlow.Finish",
|
||||
fn: func() error {
|
||||
_, err := registrationFlow.Finish(ctx, user, "devName", &wanlib.CredentialCreationResponse{})
|
||||
return err
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := test.fn()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "webauthn disabled")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginFlow_BeginFinish(t *testing.T) {
|
||||
// Simulate a previously registered U2F device.
|
||||
u2fKey, err := mocku2f.Create()
|
||||
|
|
|
@ -126,10 +126,7 @@ type RegistrationFlow struct {
|
|||
// As a side effect Begin may assign (and record in storage) a WebAuthn ID for
|
||||
// the user.
|
||||
func (f *RegistrationFlow) Begin(ctx context.Context, user string, passwordless bool) (*CredentialCreation, error) {
|
||||
switch {
|
||||
case f.Webauthn.Disabled:
|
||||
return nil, trace.BadParameter("webauthn disabled")
|
||||
case user == "":
|
||||
if user == "" {
|
||||
return nil, trace.BadParameter("user required")
|
||||
}
|
||||
|
||||
|
@ -223,8 +220,6 @@ func upsertOrGetWebID(ctx context.Context, user string, identity RegistrationIde
|
|||
// or writing the device to storage (using its Identity interface).
|
||||
func (f *RegistrationFlow) Finish(ctx context.Context, user, deviceName string, resp *CredentialCreationResponse) (*types.MFADevice, error) {
|
||||
switch {
|
||||
case f.Webauthn.Disabled:
|
||||
return nil, trace.BadParameter("webauthn disabled")
|
||||
case user == "":
|
||||
return nil, trace.BadParameter("user required")
|
||||
case deviceName == "":
|
||||
|
|
|
@ -18,12 +18,10 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -33,7 +31,6 @@ import (
|
|||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/mocku2f"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
|
@ -41,12 +38,10 @@ import (
|
|||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/teleport/lib/utils/prompt"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
u2flib "github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -82,43 +77,16 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) {
|
|||
cfg.KeysDir = t.TempDir()
|
||||
cfg.InsecureSkipVerify = true
|
||||
|
||||
// Replace (and later reset) user-prompting functions.
|
||||
oldPwd, oldOTP, oldU2F, oldWAN := *client.PasswordFromConsoleFn, *client.PromptOTP, *client.PromptU2F, *client.PromptWebauthn
|
||||
// Reset functions after tests.
|
||||
oldPwd := *client.PasswordFromConsoleFn
|
||||
t.Cleanup(func() {
|
||||
*client.PasswordFromConsoleFn = oldPwd
|
||||
*client.PromptOTP = oldOTP
|
||||
*client.PromptU2F = oldU2F
|
||||
*client.PromptWebauthn = oldWAN
|
||||
client.Prompts.Reset()
|
||||
})
|
||||
*client.PasswordFromConsoleFn = func() (string, error) {
|
||||
return password, nil
|
||||
}
|
||||
|
||||
// The loginMocks setup below is used to avoid data races when reading or
|
||||
// writing client.Prompt* pointers.
|
||||
// Tests are supposed to replace loginMocks functions instead.
|
||||
loginMocks := struct {
|
||||
promptOTP func(ctx context.Context) (string, error)
|
||||
promptU2F func(ctx context.Context, facet string, challenges ...u2flib.AuthenticateChallenge) (*u2flib.AuthenticateChallengeResponse, error)
|
||||
promptWebauthn func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error)
|
||||
}{}
|
||||
var loginMocksMU sync.RWMutex
|
||||
*client.PromptOTP = func(ctx context.Context, out io.Writer, in *prompt.ContextReader, question string) (string, error) {
|
||||
loginMocksMU.RLock()
|
||||
defer loginMocksMU.RUnlock()
|
||||
return loginMocks.promptOTP(ctx)
|
||||
}
|
||||
*client.PromptU2F = func(ctx context.Context, facet string, challenges ...u2flib.AuthenticateChallenge) (*u2flib.AuthenticateChallengeResponse, error) {
|
||||
loginMocksMU.RLock()
|
||||
defer loginMocksMU.RUnlock()
|
||||
return loginMocks.promptU2F(ctx, facet, challenges...)
|
||||
}
|
||||
*client.PromptWebauthn = func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) {
|
||||
loginMocksMU.RLock()
|
||||
defer loginMocksMU.RUnlock()
|
||||
return loginMocks.promptWebauthn(ctx, origin, assertion)
|
||||
}
|
||||
|
||||
promptOTPNoop := func(ctx context.Context) (string, error) {
|
||||
<-ctx.Done() // wait for timeout
|
||||
return "", ctx.Err()
|
||||
|
@ -131,15 +99,6 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) {
|
|||
solveOTP := func(ctx context.Context) (string, error) {
|
||||
return totp.GenerateCode(otpKey, clock.Now())
|
||||
}
|
||||
solveU2F := func(ctx context.Context, facet string, challenges ...u2flib.AuthenticateChallenge) (*u2flib.AuthenticateChallengeResponse, error) {
|
||||
kh := base64.RawURLEncoding.EncodeToString(device.KeyHandle)
|
||||
for _, c := range challenges {
|
||||
if kh == c.KeyHandle {
|
||||
return device.SignResponse(&c)
|
||||
}
|
||||
}
|
||||
return nil, trace.BadParameter("key handle now found")
|
||||
}
|
||||
solveWebauthn := func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) {
|
||||
car, err := device.SignAssertion(origin, assertion)
|
||||
if err != nil {
|
||||
|
@ -157,47 +116,34 @@ func TestTeleportClient_Login_localMFALogin(t *testing.T) {
|
|||
name string
|
||||
secondFactor constants.SecondFactorType
|
||||
solveOTP func(context.Context) (string, error)
|
||||
solveU2F func(ctx context.Context, facet string, challenges ...u2flib.AuthenticateChallenge) (*u2flib.AuthenticateChallengeResponse, error)
|
||||
solveWebauthn func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "OK OTP device login",
|
||||
secondFactor: constants.SecondFactorOptional,
|
||||
solveOTP: solveOTP,
|
||||
solveU2F: func(context.Context, string, ...u2flib.AuthenticateChallenge) (*u2flib.AuthenticateChallengeResponse, error) {
|
||||
panic("unused")
|
||||
},
|
||||
name: "OK OTP device login",
|
||||
secondFactor: constants.SecondFactorOptional,
|
||||
solveOTP: solveOTP,
|
||||
solveWebauthn: promptWebauthnNoop,
|
||||
},
|
||||
{
|
||||
name: "OK Webauthn device login",
|
||||
secondFactor: constants.SecondFactorOptional,
|
||||
solveOTP: promptOTPNoop,
|
||||
solveU2F: func(context.Context, string, ...u2flib.AuthenticateChallenge) (*u2flib.AuthenticateChallengeResponse, error) {
|
||||
panic("unused")
|
||||
},
|
||||
name: "OK Webauthn device login",
|
||||
secondFactor: constants.SecondFactorOptional,
|
||||
solveOTP: promptOTPNoop,
|
||||
solveWebauthn: solveWebauthn,
|
||||
},
|
||||
{
|
||||
name: "OK U2F device login",
|
||||
secondFactor: constants.SecondFactorU2F,
|
||||
solveOTP: promptOTPNoop,
|
||||
solveU2F: solveU2F,
|
||||
solveWebauthn: func(context.Context, string, *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) {
|
||||
panic("unused")
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
loginMocksMU.Lock()
|
||||
loginMocks.promptOTP = test.solveOTP
|
||||
loginMocks.promptU2F = test.solveU2F
|
||||
loginMocks.promptWebauthn = test.solveWebauthn
|
||||
loginMocksMU.Unlock()
|
||||
client.Prompts.Swap(
|
||||
func(ctx context.Context, out io.Writer, in *prompt.ContextReader, question string) (string, error) {
|
||||
return test.solveOTP(ctx)
|
||||
},
|
||||
func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error) {
|
||||
return test.solveWebauthn(ctx, origin, assertion)
|
||||
},
|
||||
)
|
||||
|
||||
authServer := sa.Auth.GetAuthServer()
|
||||
pref, err := authServer.GetAuthPreference(ctx)
|
||||
|
@ -258,9 +204,8 @@ func newStandaloneTeleport(t *testing.T, clock clockwork.Clock) *standaloneBundl
|
|||
cfg.Auth.Preference, err = types.NewAuthPreferenceFromConfigFile(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOptional,
|
||||
U2F: &types.U2F{
|
||||
AppID: "localhost",
|
||||
Facets: []string{"https://localhost", "localhost"},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -300,26 +245,20 @@ func newStandaloneTeleport(t *testing.T, clock clockwork.Clock) *standaloneBundl
|
|||
tokenID := token.GetName()
|
||||
res, err := authServer.CreateRegisterChallenge(ctx, &proto.CreateRegisterChallengeRequest{
|
||||
TokenID: tokenID,
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_WEBAUTHN,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
device, err := mocku2f.Create()
|
||||
require.NoError(t, err)
|
||||
registerResp, err := device.RegisterResponse(&u2f.RegisterChallenge{
|
||||
Version: res.GetU2F().GetVersion(),
|
||||
Challenge: res.GetU2F().GetChallenge(),
|
||||
AppID: res.GetU2F().GetAppID(),
|
||||
})
|
||||
const origin = "https://localhost"
|
||||
ccr, err := device.SignCredentialCreation(origin, wanlib.CredentialCreationFromProto(res.GetWebauthn()))
|
||||
require.NoError(t, err)
|
||||
_, err = authServer.ChangeUserAuthentication(ctx, &proto.ChangeUserAuthenticationRequest{
|
||||
TokenID: tokenID,
|
||||
NewPassword: []byte(password),
|
||||
NewMFARegisterResponse: &proto.MFARegisterResponse{
|
||||
Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: &proto.U2FRegisterResponse{
|
||||
RegistrationData: registerResp.RegistrationData,
|
||||
ClientData: registerResp.ClientData,
|
||||
},
|
||||
Response: &proto.MFARegisterResponse_Webauthn{
|
||||
Webauthn: wanlib.CredentialCreationResponseToProto(ccr),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -17,11 +17,5 @@ package client
|
|||
// PasswordFromConsoleFn exports passwordFromConsoleFn for tests.
|
||||
var PasswordFromConsoleFn = &passwordFromConsoleFn
|
||||
|
||||
// PromptOTP exports promptOTP for tests.
|
||||
var PromptOTP = &promptOTP
|
||||
|
||||
// PromptU2F exports promptU2F for tests.
|
||||
var PromptU2F = &promptU2F
|
||||
|
||||
// PromptWebauthn exports promptWebauthn for tests.
|
||||
var PromptWebauthn = &promptWebauthn
|
||||
// Prompts exports prompts for tests.
|
||||
var Prompts = prompts
|
||||
|
|
|
@ -19,12 +19,13 @@ package client
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/utils/prompt"
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
|
@ -32,14 +33,32 @@ import (
|
|||
wancli "github.com/gravitational/teleport/lib/auth/webauthncli"
|
||||
)
|
||||
|
||||
// promptOTP allows tests to override the OTP prompt function.
|
||||
var promptOTP = prompt.Input
|
||||
type (
|
||||
OTPPrompt func(ctx context.Context, out io.Writer, in *prompt.ContextReader, question string) (string, error)
|
||||
WebPrompt func(ctx context.Context, origin string, assertion *wanlib.CredentialAssertion) (*proto.MFAAuthenticateResponse, error)
|
||||
)
|
||||
|
||||
// promptU2F allows tests to override the U2F prompt function.
|
||||
var promptU2F = u2f.AuthenticateSignChallenge
|
||||
// PlatformPrompt groups functions that prompt the user for inputs.
|
||||
// It's purpose is to allow tests to replace actual user prompts with other
|
||||
// functions.
|
||||
type PlatformPrompt struct {
|
||||
// OTP is the OTP prompt function.
|
||||
OTP OTPPrompt
|
||||
// Webauthn is the WebAuth prompt function.
|
||||
Webauthn WebPrompt
|
||||
}
|
||||
|
||||
// promptWebauthn allows tests to override the Webauthn prompt function.
|
||||
var promptWebauthn = wancli.Login
|
||||
func (pp *PlatformPrompt) Reset() *PlatformPrompt {
|
||||
pp.Swap(prompt.Input, wancli.Login)
|
||||
return pp
|
||||
}
|
||||
|
||||
func (pp *PlatformPrompt) Swap(otp OTPPrompt, web WebPrompt) {
|
||||
pp.OTP = otp
|
||||
pp.Webauthn = web
|
||||
}
|
||||
|
||||
var prompts = (&PlatformPrompt{}).Reset()
|
||||
|
||||
// PromptMFAChallenge prompts the user to complete MFA authentication
|
||||
// challenges.
|
||||
|
@ -49,14 +68,12 @@ var promptWebauthn = wancli.Login
|
|||
// devices, like registered vs new.
|
||||
func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthenticateChallenge, promptDevicePrefix string, quiet bool) (*proto.MFAAuthenticateResponse, error) {
|
||||
// Is there a challenge present?
|
||||
if c.TOTP == nil && len(c.U2F) == 0 && c.WebauthnChallenge == nil {
|
||||
if c.TOTP == nil && c.WebauthnChallenge == nil {
|
||||
return &proto.MFAAuthenticateResponse{}, nil
|
||||
}
|
||||
|
||||
// We have three maximum challenges, from which we only pick two: TOTP and
|
||||
// either Webauthn (preferred) or U2F.
|
||||
hasTOTP := c.TOTP != nil
|
||||
hasNonTOTP := len(c.U2F) > 0 || c.WebauthnChallenge != nil
|
||||
hasWebauthn := c.WebauthnChallenge != nil
|
||||
|
||||
// Does the current platform support hardware MFA? Adjust accordingly.
|
||||
switch {
|
||||
|
@ -64,11 +81,11 @@ func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthe
|
|||
return nil, trace.BadParameter("hardware device MFA not supported by your platform, please register an OTP device")
|
||||
case !wancli.HasPlatformSupport():
|
||||
// Do not prompt for hardware devices, it won't work.
|
||||
hasNonTOTP = false
|
||||
hasWebauthn = false
|
||||
}
|
||||
|
||||
var numGoroutines int
|
||||
if hasTOTP && hasNonTOTP {
|
||||
if hasTOTP && hasWebauthn {
|
||||
numGoroutines = 2
|
||||
} else {
|
||||
numGoroutines = 1
|
||||
|
@ -81,19 +98,30 @@ func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthe
|
|||
}
|
||||
respC := make(chan response, numGoroutines)
|
||||
|
||||
// Use ctx and wg to clean up after ourselves.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
var wg sync.WaitGroup
|
||||
cancelAndWait := func() {
|
||||
cancel()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Fire TOTP goroutine.
|
||||
if hasTOTP {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
const kind = "TOTP"
|
||||
var msg string
|
||||
if !quiet {
|
||||
if hasNonTOTP {
|
||||
if hasWebauthn {
|
||||
msg = fmt.Sprintf("Tap any %[1]ssecurity key or enter a code from a %[1]sOTP device", promptDevicePrefix, promptDevicePrefix)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Enter an OTP code from a %sdevice", promptDevicePrefix)
|
||||
}
|
||||
}
|
||||
code, err := promptOTP(ctx, os.Stderr, prompt.Stdin(), msg)
|
||||
code, err := prompts.OTP(ctx, os.Stderr, prompt.Stdin(), msg)
|
||||
if err != nil {
|
||||
respC <- response{kind: kind, err: err}
|
||||
return
|
||||
|
@ -111,24 +139,19 @@ func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthe
|
|||
fmt.Fprintf(os.Stderr, "Tap any %ssecurity key\n", promptDevicePrefix)
|
||||
}
|
||||
|
||||
// Fire Webauthn or U2F goroutine.
|
||||
origin := proxyAddr
|
||||
if !strings.HasPrefix(origin, "https://") {
|
||||
origin = "https://" + origin
|
||||
}
|
||||
switch {
|
||||
case c.WebauthnChallenge != nil:
|
||||
// Fire Webauthn goroutine.
|
||||
if hasWebauthn {
|
||||
origin := proxyAddr
|
||||
if !strings.HasPrefix(origin, "https://") {
|
||||
origin = "https://" + origin
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
log.Debugf("WebAuthn: prompting U2F devices with origin %q", origin)
|
||||
resp, err := promptWebauthn(ctx, origin, wanlib.CredentialAssertionFromProto(c.WebauthnChallenge))
|
||||
defer wg.Done()
|
||||
log.Debugf("WebAuthn: prompting devices with origin %q", origin)
|
||||
resp, err := prompts.Webauthn(ctx, origin, wanlib.CredentialAssertionFromProto(c.WebauthnChallenge))
|
||||
respC <- response{kind: "WEBAUTHN", resp: resp, err: err}
|
||||
}()
|
||||
case len(c.U2F) > 0:
|
||||
go func() {
|
||||
log.Debugf("prompting U2F devices with facet %q", origin)
|
||||
resp, err := promptU2FChallenges(ctx, proxyAddr, c.U2F)
|
||||
respC <- response{kind: "U2F", resp: resp, err: err}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
|
@ -143,63 +166,27 @@ func PromptMFAChallenge(ctx context.Context, proxyAddr string, c *proto.MFAAuthe
|
|||
fmt.Fprintln(os.Stderr) // Print a new line after the prompt
|
||||
}
|
||||
|
||||
// Exiting cancels the context via defer, which makes the remaining
|
||||
// goroutines stop.
|
||||
// Cleanup in-flight goroutines.
|
||||
cancelAndWait()
|
||||
return resp.resp, nil
|
||||
case <-ctx.Done():
|
||||
cancelAndWait()
|
||||
return nil, trace.Wrap(ctx.Err())
|
||||
}
|
||||
}
|
||||
cancelAndWait()
|
||||
return nil, trace.BadParameter(
|
||||
"failed to authenticate using all MFA devices, rerun the command with '-d' to see error details for each device")
|
||||
}
|
||||
|
||||
func promptU2FChallenges(ctx context.Context, origin string, challenges []*proto.U2FChallenge) (*proto.MFAAuthenticateResponse, error) {
|
||||
u2fChallenges := make([]u2f.AuthenticateChallenge, 0, len(challenges))
|
||||
for _, chal := range challenges {
|
||||
u2fChallenges = append(u2fChallenges, u2f.AuthenticateChallenge{
|
||||
Challenge: chal.Challenge,
|
||||
KeyHandle: chal.KeyHandle,
|
||||
AppID: chal.AppID,
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := promptU2F(ctx, origin, u2fChallenges...)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_U2F{
|
||||
U2F: &proto.U2FResponse{
|
||||
KeyHandle: resp.KeyHandle,
|
||||
ClientData: resp.ClientData,
|
||||
Signature: resp.SignatureData,
|
||||
},
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// MakeAuthenticateChallenge converts proto to JSON format.
|
||||
func MakeAuthenticateChallenge(protoChal *proto.MFAAuthenticateChallenge) *auth.MFAAuthenticateChallenge {
|
||||
chal := &auth.MFAAuthenticateChallenge{
|
||||
TOTPChallenge: protoChal.GetTOTP() != nil,
|
||||
}
|
||||
|
||||
for _, u2fChal := range protoChal.GetU2F() {
|
||||
ch := u2f.AuthenticateChallenge{
|
||||
Version: u2fChal.Version,
|
||||
Challenge: u2fChal.Challenge,
|
||||
KeyHandle: u2fChal.KeyHandle,
|
||||
AppID: u2fChal.AppID,
|
||||
}
|
||||
if chal.AuthenticateChallenge == nil {
|
||||
chal.AuthenticateChallenge = &ch
|
||||
}
|
||||
chal.U2FChallenges = append(chal.U2FChallenges, ch)
|
||||
}
|
||||
|
||||
if protoChal.GetWebauthnChallenge() != nil {
|
||||
chal.WebauthnChallenge = wanlib.CredentialAssertionFromProto(protoChal.WebauthnChallenge)
|
||||
}
|
||||
|
||||
return chal
|
||||
}
|
||||
|
||||
|
@ -209,8 +196,6 @@ type TOTPRegisterChallenge struct {
|
|||
|
||||
// MFARegisterChallenge is an MFA register challenge sent on new MFA register.
|
||||
type MFARegisterChallenge struct {
|
||||
// U2F contains U2F register challenge.
|
||||
U2F *u2f.RegisterChallenge `json:"u2f"`
|
||||
// Webauthn contains webauthn challenge.
|
||||
Webauthn *wanlib.CredentialCreation `json:"webauthn"`
|
||||
// TOTP contains TOTP challenge.
|
||||
|
@ -226,21 +211,10 @@ func MakeRegisterChallenge(protoChal *proto.MFARegisterChallenge) *MFARegisterCh
|
|||
QRCode: protoChal.GetTOTP().GetQRCode(),
|
||||
},
|
||||
}
|
||||
|
||||
case *proto.MFARegisterChallenge_U2F:
|
||||
return &MFARegisterChallenge{
|
||||
U2F: &u2f.RegisterChallenge{
|
||||
Version: protoChal.GetU2F().GetVersion(),
|
||||
Challenge: protoChal.GetU2F().GetChallenge(),
|
||||
AppID: protoChal.GetU2F().GetAppID(),
|
||||
},
|
||||
}
|
||||
|
||||
case *proto.MFARegisterChallenge_Webauthn:
|
||||
return &MFARegisterChallenge{
|
||||
Webauthn: wanlib.CredentialCreationFromProto(protoChal.GetWebauthn()),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ func runPresenceTask(ctx context.Context, term io.Writer, auth auth.ClientI, tc
|
|||
func solveMFA(ctx context.Context, term io.Writer, tc *TeleportClient, challenge *proto.MFAAuthenticateChallenge) (*proto.MFAAuthenticateResponse, error) {
|
||||
fmt.Fprint(term, "\r\nTeleport > Please tap your MFA key\r\n")
|
||||
|
||||
// This is here to enforce the usage of an U2F or WebAuthn device.
|
||||
// This is here to enforce the usage of a MFA device.
|
||||
// We don't support TOTP for live presence.
|
||||
challenge.TOTP = nil
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ import (
|
|||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -125,8 +124,6 @@ type AuthenticateSSHUserRequest struct {
|
|||
// Password for the user, to authenticate in case no MFA check was
|
||||
// performed.
|
||||
Password string `json:"password"`
|
||||
// U2FSignResponse is the signature from the U2F device
|
||||
U2FSignResponse *u2f.AuthenticateChallengeResponse `json:"u2f_sign_response"`
|
||||
// WebauthnChallengeResponse is a signed WebAuthn credential assertion.
|
||||
WebauthnChallengeResponse *wanlib.CredentialAssertionResponse `json:"webauthn_challenge_response"`
|
||||
// TOTPCode is a code from the TOTP device.
|
||||
|
@ -149,8 +146,6 @@ type AuthenticateSSHUserRequest struct {
|
|||
type AuthenticateWebUserRequest struct {
|
||||
// User is a teleport username.
|
||||
User string `json:"user"`
|
||||
// U2FSignResponse is the signature from the U2F device.
|
||||
U2FSignResponse *u2f.AuthenticateChallengeResponse `json:"u2f_sign_response,omitempty"`
|
||||
// WebauthnAssertionResponse is a signed WebAuthn credential assertion.
|
||||
WebauthnAssertionResponse *wanlib.CredentialAssertionResponse `json:"webauthnAssertionResponse,omitempty"`
|
||||
}
|
||||
|
@ -353,10 +348,10 @@ func SSHAgentLogin(ctx context.Context, login SSHLoginDirect) (*auth.SSHLoginRes
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// SSHAgentMFALogin requests a MFA challenge (U2F or OTP) via the proxy. If the
|
||||
// credentials are valid, the proxy wiil return a challenge. We then prompt the
|
||||
// user to provide 2nd factor and pass the response to the proxy. If the
|
||||
// authentication succeeds, we will get a temporary certificate back.
|
||||
// SSHAgentMFALogin requests a MFA challenge via the proxy.
|
||||
// If the credentials are valid, the proxy will return a challenge. We then
|
||||
// prompt the user to provide 2nd factor and pass the response to the proxy.
|
||||
// If the authentication succeeds, we will get a temporary certificate back.
|
||||
func SSHAgentMFALogin(ctx context.Context, login SSHLoginMFA) (*auth.SSHLoginResponse, error) {
|
||||
clt, _, err := initClient(login.ProxyAddr, login.Insecure, login.Pool)
|
||||
if err != nil {
|
||||
|
@ -367,13 +362,7 @@ func SSHAgentMFALogin(ctx context.Context, login SSHLoginMFA) (*auth.SSHLoginRes
|
|||
User: login.User,
|
||||
Pass: login.Password,
|
||||
}
|
||||
// TODO(codingllama): Decide endpoints based on the Ping server version, once
|
||||
// we have a known version for Webauthn.
|
||||
challengeJSON, err := clt.PostJSON(ctx, clt.Endpoint("webapi", "mfa", "login", "begin"), beginReq)
|
||||
// DELETE IN 9.x, fallback not necessary after U2F is removed (codingllama)
|
||||
if trace.IsNotImplemented(err) {
|
||||
challengeJSON, err = clt.PostJSON(ctx, clt.Endpoint("webapi", "u2f", "signrequest"), beginReq)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -382,25 +371,12 @@ func SSHAgentMFALogin(ctx context.Context, login SSHLoginMFA) (*auth.SSHLoginRes
|
|||
if err := json.Unmarshal(challengeJSON.Bytes(), challenge); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
// DELETE IN 9.x, fallback not necessary after U2F is removed (codingllama)
|
||||
if len(challenge.U2FChallenges) == 0 && challenge.AuthenticateChallenge != nil {
|
||||
// Challenge sent by a pre-6.0 auth server, fall back to the old
|
||||
// single-device format.
|
||||
challenge.U2FChallenges = []u2f.AuthenticateChallenge{*challenge.AuthenticateChallenge}
|
||||
}
|
||||
|
||||
// Convert to auth gRPC proto challenge.
|
||||
challengePB := &proto.MFAAuthenticateChallenge{}
|
||||
if challenge.TOTPChallenge {
|
||||
challengePB.TOTP = &proto.TOTPChallenge{}
|
||||
}
|
||||
for _, c := range challenge.U2FChallenges {
|
||||
challengePB.U2F = append(challengePB.U2F, &proto.U2FChallenge{
|
||||
KeyHandle: c.KeyHandle,
|
||||
Challenge: c.Challenge,
|
||||
AppID: c.AppID,
|
||||
})
|
||||
}
|
||||
if challenge.WebauthnChallenge != nil {
|
||||
challengePB.WebauthnChallenge = wanlib.CredentialAssertionToProto(challenge.WebauthnChallenge)
|
||||
}
|
||||
|
@ -423,12 +399,6 @@ func SSHAgentMFALogin(ctx context.Context, login SSHLoginMFA) (*auth.SSHLoginRes
|
|||
switch r := respPB.Response.(type) {
|
||||
case *proto.MFAAuthenticateResponse_TOTP:
|
||||
challengeResp.TOTPCode = r.TOTP.Code
|
||||
case *proto.MFAAuthenticateResponse_U2F:
|
||||
challengeResp.U2FSignResponse = &u2f.AuthenticateChallengeResponse{
|
||||
KeyHandle: r.U2F.KeyHandle,
|
||||
SignatureData: r.U2F.Signature,
|
||||
ClientData: r.U2F.ClientData,
|
||||
}
|
||||
case *proto.MFAAuthenticateResponse_Webauthn:
|
||||
challengeResp.WebauthnChallengeResponse = wanlib.CredentialAssertionResponseFromProto(r.Webauthn)
|
||||
default:
|
||||
|
@ -436,10 +406,6 @@ func SSHAgentMFALogin(ctx context.Context, login SSHLoginMFA) (*auth.SSHLoginRes
|
|||
}
|
||||
|
||||
loginRespJSON, err := clt.PostJSON(ctx, clt.Endpoint("webapi", "mfa", "login", "finish"), challengeResp)
|
||||
// DELETE IN 9.x, fallback not necessary after U2F is removed (codingllama)
|
||||
if trace.IsNotImplemented(err) {
|
||||
loginRespJSON, err = clt.PostJSON(ctx, clt.Endpoint("webapi", "u2f", "certs"), challengeResp)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ import (
|
|||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
@ -745,7 +745,10 @@ type Webauthn struct {
|
|||
RPID string `yaml:"rp_id,omitempty"`
|
||||
AttestationAllowedCAs []string `yaml:"attestation_allowed_cas,omitempty"`
|
||||
AttestationDeniedCAs []string `yaml:"attestation_denied_cas,omitempty"`
|
||||
Disabled bool `yaml:"disabled,omitempty"`
|
||||
// Disabled has no effect, it is kept solely to not break existing
|
||||
// configurations.
|
||||
// DELETE IN 11.0, time to sunset U2F (codingllama).
|
||||
Disabled bool `yaml:"disabled,omitempty"`
|
||||
}
|
||||
|
||||
func (w *Webauthn) Parse() (*types.Webauthn, error) {
|
||||
|
@ -757,13 +760,18 @@ func (w *Webauthn) Parse() (*types.Webauthn, error) {
|
|||
if err != nil {
|
||||
return nil, trace.BadParameter("webauthn.attestation_denied_cas: %v", err)
|
||||
}
|
||||
if w.Disabled {
|
||||
log.Warnf(`` +
|
||||
`The "webauthn.disabled" setting is marked for removal and currently has no effect. ` +
|
||||
`Please update your configuration to use WebAuthn. ` +
|
||||
`Refer to https://goteleport.com/docs/access-controls/guides/webauthn/`)
|
||||
}
|
||||
return &types.Webauthn{
|
||||
// Allow any RPID to go through, we rely on
|
||||
// types.Webauthn.CheckAndSetDefaults to correct it.
|
||||
RPID: w.RPID,
|
||||
AttestationAllowedCAs: allowedCAs,
|
||||
AttestationDeniedCAs: deniedCAs,
|
||||
Disabled: w.Disabled,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -278,7 +278,7 @@ func TestAuthenticationSection(t *testing.T) {
|
|||
},
|
||||
},
|
||||
"webauthn": cfgMap{
|
||||
"disabled": true,
|
||||
"disabled": true, // Kept for backwards compatibility, has no effect.
|
||||
},
|
||||
}
|
||||
},
|
||||
|
|
|
@ -704,9 +704,6 @@ const (
|
|||
// WebsocketResize is receiving a resize request.
|
||||
WebsocketResize = "w"
|
||||
|
||||
// WebsocketU2FChallenge is sending a U2F challenge.
|
||||
WebsocketU2FChallenge = "u"
|
||||
|
||||
// WebsocketWebauthnChallenge is sending a webauthn challenge.
|
||||
WebsocketWebauthnChallenge = "n"
|
||||
)
|
||||
|
|
|
@ -34,7 +34,6 @@ import (
|
|||
|
||||
authproto "github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/web/mfajson"
|
||||
|
@ -457,7 +456,7 @@ func decodeClipboardData(in peekReader, maxLen uint32) (ClipboardData, error) {
|
|||
const maxMFADataLength = 1024 * 1024
|
||||
|
||||
type MFA struct {
|
||||
// Type should be one of defaults.WebsocketU2FChallenge or defaults.WebsocketWebauthnChallenge
|
||||
// Type should be defaults.WebsocketWebauthnChallenge
|
||||
Type byte
|
||||
// MFAAuthenticateChallenge is the challenge we send to the client.
|
||||
// Used for messages from Teleport to the user's browser.
|
||||
|
@ -481,17 +480,6 @@ func (m MFA) Encode() ([]byte, error) {
|
|||
}
|
||||
} else if m.MFAAuthenticateResponse != nil {
|
||||
switch t := m.MFAAuthenticateResponse.Response.(type) {
|
||||
case *authproto.MFAAuthenticateResponse_U2F:
|
||||
msg := m.MFAAuthenticateResponse.GetU2F()
|
||||
resp := u2f.AuthenticateChallengeResponse{
|
||||
KeyHandle: msg.KeyHandle,
|
||||
SignatureData: msg.Signature,
|
||||
ClientData: msg.ClientData,
|
||||
}
|
||||
buff, err = json.Marshal(resp)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
case *authproto.MFAAuthenticateResponse_Webauthn:
|
||||
buff, err = json.Marshal(wanlib.CredentialAssertionResponseFromProto(m.MFAAuthenticateResponse.GetWebauthn()))
|
||||
if err != nil {
|
||||
|
@ -527,10 +515,10 @@ func DecodeMFA(in peekReader) (*MFA, error) {
|
|||
}
|
||||
s := string(mt)
|
||||
switch s {
|
||||
case defaults.WebsocketWebauthnChallenge, defaults.WebsocketU2FChallenge:
|
||||
case defaults.WebsocketWebauthnChallenge:
|
||||
default:
|
||||
return nil, trace.BadParameter("got mfa type %v, expected %v (WebAuthn) or %v (U2F)",
|
||||
mt, defaults.WebsocketWebauthnChallenge, defaults.WebsocketU2FChallenge)
|
||||
return nil, trace.BadParameter(
|
||||
"got mfa type %v, expected %v (WebAuthn)", mt, defaults.WebsocketWebauthnChallenge)
|
||||
}
|
||||
|
||||
var length uint32
|
||||
|
@ -575,10 +563,10 @@ func DecodeMFAChallenge(in peekReader) (*MFA, error) {
|
|||
}
|
||||
s := string(mt)
|
||||
switch s {
|
||||
case defaults.WebsocketWebauthnChallenge, defaults.WebsocketU2FChallenge:
|
||||
case defaults.WebsocketWebauthnChallenge:
|
||||
default:
|
||||
return nil, trace.BadParameter("got mfa type %v, expected %v (WebAuthn) or %v (U2F)",
|
||||
mt, defaults.WebsocketWebauthnChallenge, defaults.WebsocketU2FChallenge)
|
||||
return nil, trace.BadParameter(
|
||||
"got mfa type %v, expected %v (WebAuthn)", mt, defaults.WebsocketWebauthnChallenge)
|
||||
}
|
||||
|
||||
var length uint32
|
||||
|
|
|
@ -27,14 +27,16 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/duo-labs/webauthn/protocol"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
authproto "github.com/gravitational/teleport/api/client/proto"
|
||||
wantypes "github.com/gravitational/teleport/api/types/webauthn"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
)
|
||||
|
||||
|
@ -135,15 +137,26 @@ func loadBitmaps(b *testing.B) []PNGFrame {
|
|||
func TestMFA(t *testing.T) {
|
||||
var buff bytes.Buffer
|
||||
c := NewConn(&buff)
|
||||
|
||||
mfaWant := &MFA{
|
||||
Type: defaults.WebsocketU2FChallenge[0],
|
||||
Type: defaults.WebsocketWebauthnChallenge[0],
|
||||
MFAAuthenticateChallenge: &auth.MFAAuthenticateChallenge{
|
||||
U2FChallenges: []u2f.AuthenticateChallenge{
|
||||
{
|
||||
Version: "version",
|
||||
Challenge: "challenge",
|
||||
KeyHandle: "key_handle",
|
||||
AppID: "app_id",
|
||||
WebauthnChallenge: &wanlib.CredentialAssertion{
|
||||
Response: protocol.PublicKeyCredentialRequestOptions{
|
||||
Challenge: []byte("challenge"),
|
||||
Timeout: 10,
|
||||
RelyingPartyID: "teleport",
|
||||
AllowedCredentials: []protocol.CredentialDescriptor{
|
||||
{
|
||||
Type: "public-key",
|
||||
CredentialID: []byte("credential id"),
|
||||
Transport: []protocol.AuthenticatorTransport{protocol.USB},
|
||||
},
|
||||
},
|
||||
UserVerification: "discouraged",
|
||||
Extensions: protocol.AuthenticationExtensions{
|
||||
"ext1": "value1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -155,13 +168,21 @@ func TestMFA(t *testing.T) {
|
|||
require.Equal(t, mfaWant, mfaGot)
|
||||
|
||||
respWant := &MFA{
|
||||
Type: defaults.WebsocketU2FChallenge[0],
|
||||
Type: defaults.WebsocketWebauthnChallenge[0],
|
||||
MFAAuthenticateResponse: &authproto.MFAAuthenticateResponse{
|
||||
Response: &authproto.MFAAuthenticateResponse_U2F{
|
||||
U2F: &authproto.U2FResponse{
|
||||
KeyHandle: "key_handler",
|
||||
ClientData: "client_data",
|
||||
Signature: "signature",
|
||||
Response: &authproto.MFAAuthenticateResponse_Webauthn{
|
||||
Webauthn: &wantypes.CredentialAssertionResponse{
|
||||
Type: "public-key",
|
||||
RawId: []byte("credential id"),
|
||||
Response: &wantypes.AuthenticatorAssertionResponse{
|
||||
ClientDataJson: []byte("client data json"),
|
||||
AuthenticatorData: []byte("authenticator data"),
|
||||
Signature: []byte("signature"),
|
||||
UserHandle: []byte("user handle"),
|
||||
},
|
||||
Extensions: &wantypes.AuthenticationExtensionsClientOutputs{
|
||||
AppId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -44,7 +44,6 @@ import (
|
|||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
apisshutils "github.com/gravitational/teleport/api/utils/sshutils"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
|
@ -361,14 +360,6 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) {
|
|||
h.GET("/webapi/github/callback", h.WithMetaRedirect(h.githubCallback))
|
||||
h.POST("/webapi/github/login/console", httplib.MakeHandler(h.githubLoginConsole))
|
||||
|
||||
// U2F related APIs
|
||||
// DELETE IN 9.x, superseded by /mfa/ endpoints (codingllama)
|
||||
h.GET("/webapi/u2f/signuptokens/:token", httplib.MakeHandler(h.u2fRegisterRequest)) // replaced with /webapi/mfa/token/:token/registerchallenge
|
||||
h.POST("/webapi/u2f/password/changerequest", h.WithAuth(h.createAuthenticateChallengeWithPassword)) // replaced with /webapi/mfa/authenticatechallenge/password
|
||||
h.POST("/webapi/u2f/signrequest", httplib.MakeHandler(h.mfaLoginBegin)) // replaced with /webapi/mfa/login/begin
|
||||
h.POST("/webapi/u2f/sessions", httplib.MakeHandler(h.mfaLoginFinishSession)) // replaced with /webapi/mfa/login/finishsession
|
||||
h.POST("/webapi/u2f/certs", httplib.MakeHandler(h.mfaLoginFinish)) // replaced with /webapi/mfa/login/finish
|
||||
|
||||
// MFA public endpoints.
|
||||
h.POST("/webapi/mfa/login/begin", httplib.MakeHandler(h.mfaLoginBegin))
|
||||
h.POST("/webapi/mfa/login/finish", httplib.MakeHandler(h.mfaLoginFinish))
|
||||
|
@ -1518,8 +1509,6 @@ type changeUserAuthenticationRequest struct {
|
|||
TokenID string `json:"token"`
|
||||
// Password is user password string converted to bytes.
|
||||
Password []byte `json:"password"`
|
||||
// U2FRegisterResponse is U2F registration challenge response.
|
||||
U2FRegisterResponse *u2f.RegisterChallengeResponse `json:"u2f_register_response,omitempty"`
|
||||
// WebauthnCreationResponse is the signed credential creation response.
|
||||
WebauthnCreationResponse *wanlib.CredentialCreationResponse `json:"webauthnCreationResponse"`
|
||||
}
|
||||
|
@ -1541,13 +1530,6 @@ func (h *Handler) changeUserAuthentication(w http.ResponseWriter, r *http.Reques
|
|||
Webauthn: wanlib.CredentialCreationResponseToProto(req.WebauthnCreationResponse),
|
||||
},
|
||||
}
|
||||
case req.U2FRegisterResponse != nil:
|
||||
protoReq.NewMFARegisterResponse = &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: &proto.U2FRegisterResponse{
|
||||
RegistrationData: req.U2FRegisterResponse.RegistrationData,
|
||||
ClientData: req.U2FRegisterResponse.ClientData,
|
||||
},
|
||||
}}
|
||||
case req.SecondFactorToken != "":
|
||||
protoReq.NewMFARegisterResponse = &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_TOTP{
|
||||
TOTP: &proto.TOTPRegisterResponse{Code: req.SecondFactorToken},
|
||||
|
@ -1650,38 +1632,16 @@ func (h *Handler) getResetPasswordToken(ctx context.Context, tokenID string) (in
|
|||
}, nil
|
||||
}
|
||||
|
||||
// u2fRegisterRequest is called to get a U2F challenge for registering a U2F key
|
||||
//
|
||||
// GET /webapi/u2f/signuptokens/:token
|
||||
//
|
||||
// Response:
|
||||
//
|
||||
// {"version":"U2F_V2","challenge":"randombase64string","appId":"https://mycorp.com:3080"}
|
||||
//
|
||||
func (h *Handler) u2fRegisterRequest(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
res, err := h.auth.proxyClient.CreateRegisterChallenge(r.Context(), &proto.CreateRegisterChallengeRequest{
|
||||
TokenID: p.ByName("token"),
|
||||
DeviceType: proto.DeviceType_DEVICE_TYPE_U2F,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
chal := client.MakeRegisterChallenge(res)
|
||||
return chal.U2F, nil
|
||||
}
|
||||
|
||||
// mfaLoginBegin is the first step in the MFA authentication ceremony, which
|
||||
// may be completed either via mfaLoginFinish (SSH) or mfaLoginFinishSession (Web).
|
||||
//
|
||||
// POST /webapi/u2f/signrequest (deprecated)
|
||||
// POST /webapi/mfa/login/begin
|
||||
//
|
||||
// {"user": "alex", "pass": "abc123"}
|
||||
//
|
||||
// Successful response:
|
||||
//
|
||||
// {"webauthn_challenge": {...}, "u2f_challenges": [...], "totp_challenge": true}
|
||||
// {"webauthn_challenge": {...}, "totp_challenge": true}
|
||||
func (h *Handler) mfaLoginBegin(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
|
||||
var req *client.MFAChallengeRequest
|
||||
if err := httplib.ReadJSON(r, &req); err != nil {
|
||||
|
@ -1703,7 +1663,6 @@ func (h *Handler) mfaLoginBegin(w http.ResponseWriter, r *http.Request, p httpro
|
|||
// mfaLoginFinish completes the MFA login ceremony, returning a new SSH
|
||||
// certificate if successful.
|
||||
//
|
||||
// POST /v1/webapi/u2f/certs (deprecated)
|
||||
// POST /v1/mfa/login/finish
|
||||
//
|
||||
// { "user": "bob", "password": "pass", "pub_key": "key to sign", "ttl": 1000000000 } # password-only
|
||||
|
@ -1728,7 +1687,6 @@ func (h *Handler) mfaLoginFinish(w http.ResponseWriter, r *http.Request, p httpr
|
|||
// mfaLoginFinishSession completes the MFA login ceremony, returning a new web
|
||||
// session if successful.
|
||||
//
|
||||
// POST /webapi/u2f/session (deprecated)
|
||||
// POST /webapi/mfa/login/finishsession
|
||||
//
|
||||
// {"user": "alex", "webauthn_challenge_response": {...}}
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -29,133 +28,24 @@ import (
|
|||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestU2FLogin(t *testing.T) {
|
||||
for _, sf := range []constants.SecondFactorType{
|
||||
constants.SecondFactorU2F,
|
||||
constants.SecondFactorOptional,
|
||||
constants.SecondFactorOn,
|
||||
} {
|
||||
sf := sf
|
||||
t.Run(fmt.Sprintf("second_factor_%s", sf), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testU2FLogin(t, sf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testU2FLogin(t *testing.T, sf constants.SecondFactorType) {
|
||||
env := newWebPack(t, 1)
|
||||
clusterMFA := configureClusterForMFA(t, env, &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: sf,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://" + env.server.TLS.ClusterName(),
|
||||
Facets: []string{"https://" + env.server.TLS.ClusterName()},
|
||||
},
|
||||
})
|
||||
user := clusterMFA.User
|
||||
password := clusterMFA.Password
|
||||
device := clusterMFA.U2FDev.Key
|
||||
|
||||
// normal login
|
||||
clt, err := client.NewWebClient(env.proxies[0].webURL.String(), roundtrip.HTTPClient(client.NewInsecureWebClient()))
|
||||
require.NoError(t, err)
|
||||
re, err := clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "signrequest"), client.MFAChallengeRequest{
|
||||
User: user,
|
||||
Pass: password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
var u2fSignReq u2f.AuthenticateChallenge
|
||||
require.NoError(t, json.Unmarshal(re.Bytes(), &u2fSignReq))
|
||||
|
||||
u2fSignResp, err := device.SignResponse(&u2fSignReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "sessions"), &client.AuthenticateWebUserRequest{
|
||||
User: user,
|
||||
U2FSignResponse: u2fSignResp,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// bad login: corrupted sign responses, should fail
|
||||
re, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "signrequest"), client.MFAChallengeRequest{
|
||||
User: user,
|
||||
Pass: password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(re.Bytes(), &u2fSignReq))
|
||||
|
||||
u2fSignResp, err = device.SignResponse(&u2fSignReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
// corrupted KeyHandle
|
||||
u2fSignRespCopy := u2fSignResp
|
||||
u2fSignRespCopy.KeyHandle = u2fSignRespCopy.KeyHandle + u2fSignRespCopy.KeyHandle
|
||||
_, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "sessions"), &client.AuthenticateWebUserRequest{
|
||||
User: user,
|
||||
U2FSignResponse: u2fSignRespCopy,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
// corrupted SignatureData
|
||||
u2fSignRespCopy = u2fSignResp
|
||||
u2fSignRespCopy.SignatureData = u2fSignRespCopy.SignatureData[:10] + u2fSignRespCopy.SignatureData[20:]
|
||||
|
||||
_, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "sessions"), &client.AuthenticateWebUserRequest{
|
||||
User: user,
|
||||
U2FSignResponse: u2fSignRespCopy,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
// corrupted ClientData
|
||||
u2fSignRespCopy = u2fSignResp
|
||||
u2fSignRespCopy.ClientData = u2fSignRespCopy.ClientData[:10] + u2fSignRespCopy.ClientData[20:]
|
||||
|
||||
_, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "sessions"), &client.AuthenticateWebUserRequest{
|
||||
User: user,
|
||||
U2FSignResponse: u2fSignRespCopy,
|
||||
})
|
||||
require.Error(t, err)
|
||||
|
||||
// bad login: counter not increasing, should fail
|
||||
device.SetCounter(0)
|
||||
re, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "signrequest"), client.MFAChallengeRequest{
|
||||
User: user,
|
||||
Pass: password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(re.Bytes(), &u2fSignReq))
|
||||
|
||||
u2fSignResp, err = device.SignResponse(&u2fSignReq)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = clt.PostJSON(context.Background(), clt.Endpoint("webapi", "u2f", "sessions"), &client.AuthenticateWebUserRequest{
|
||||
User: user,
|
||||
U2FSignResponse: u2fSignResp,
|
||||
})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestWebauthnLogin_ssh_u2fDevice(t *testing.T) {
|
||||
func TestWebauthnLogin_ssh(t *testing.T) {
|
||||
env := newWebPack(t, 1)
|
||||
clusterMFA := configureClusterForMFA(t, env, &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOn,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://" + env.server.TLS.ClusterName(),
|
||||
Facets: []string{"https://" + env.server.TLS.ClusterName()},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: env.server.TLS.ClusterName(),
|
||||
},
|
||||
// Use default Webauthn configuration.
|
||||
})
|
||||
user := clusterMFA.User
|
||||
password := clusterMFA.Password
|
||||
device := clusterMFA.U2FDev.Key
|
||||
device := clusterMFA.WebDev.Key
|
||||
|
||||
clt, err := client.NewWebClient(env.proxies[0].webURL.String(), roundtrip.HTTPClient(client.NewInsecureWebClient()))
|
||||
require.NoError(t, err)
|
||||
|
@ -199,20 +89,19 @@ func TestWebauthnLogin_ssh_u2fDevice(t *testing.T) {
|
|||
require.NotEmpty(t, loginResp.HostSigners)
|
||||
}
|
||||
|
||||
func TestWebauthnLogin_web_u2fDevice(t *testing.T) {
|
||||
func TestWebauthnLogin_web(t *testing.T) {
|
||||
env := newWebPack(t, 1)
|
||||
clusterMFA := configureClusterForMFA(t, env, &types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOn,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://" + env.server.TLS.ClusterName(),
|
||||
Facets: []string{"https://" + env.server.TLS.ClusterName()},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: env.server.TLS.ClusterName(),
|
||||
},
|
||||
// Use default Webauthn configuration.
|
||||
})
|
||||
user := clusterMFA.User
|
||||
password := clusterMFA.Password
|
||||
device := clusterMFA.U2FDev.Key
|
||||
device := clusterMFA.WebDev.Key
|
||||
|
||||
clt, err := client.NewWebClient(env.proxies[0].webURL.String(), roundtrip.HTTPClient(client.NewInsecureWebClient()))
|
||||
require.NoError(t, err)
|
||||
|
@ -249,7 +138,7 @@ func TestWebauthnLogin_web_u2fDevice(t *testing.T) {
|
|||
|
||||
type configureMFAResp struct {
|
||||
User, Password string
|
||||
U2FDev *auth.TestDevice
|
||||
WebDev *auth.TestDevice
|
||||
}
|
||||
|
||||
func configureClusterForMFA(t *testing.T, env *webPack, spec *types.AuthPreferenceSpecV2) *configureMFAResp {
|
||||
|
@ -271,12 +160,12 @@ func configureClusterForMFA(t *testing.T, env *webPack, spec *types.AuthPreferen
|
|||
// Register device.
|
||||
clt, err := env.server.NewClient(auth.TestUser(user))
|
||||
require.NoError(t, err)
|
||||
u2fDev, err := auth.RegisterTestDevice(ctx, clt, "u2f", proto.DeviceType_DEVICE_TYPE_U2F, nil /* authenticator */)
|
||||
webDev, err := auth.RegisterTestDevice(ctx, clt, "webauthn", proto.DeviceType_DEVICE_TYPE_WEBAUTHN, nil /* authenticator */)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &configureMFAResp{
|
||||
User: user,
|
||||
Password: password,
|
||||
U2FDev: u2fDev,
|
||||
WebDev: webDev,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,6 @@ import (
|
|||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/mocku2f"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/bpf"
|
||||
|
@ -1092,49 +1091,6 @@ func TestTerminalRequireSessionMfa(t *testing.T) {
|
|||
return webauthnResBytes
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with u2f",
|
||||
getAuthPreference: func() types.AuthPreference {
|
||||
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorU2F,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
},
|
||||
RequireSessionMFA: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return ap
|
||||
},
|
||||
registerDevice: func() *auth.TestDevice {
|
||||
u2fDev, err := auth.RegisterTestDevice(ctx, clt, "u2f", apiProto.DeviceType_DEVICE_TYPE_U2F, nil /* authenticator */)
|
||||
require.NoError(t, err)
|
||||
|
||||
return u2fDev
|
||||
},
|
||||
getChallengeResponseBytes: func(chals *auth.MFAAuthenticateChallenge, dev *auth.TestDevice) []byte {
|
||||
res, err := dev.SolveAuthn(&apiProto.MFAAuthenticateChallenge{
|
||||
U2F: []*apiProto.U2FChallenge{{
|
||||
KeyHandle: chals.U2FChallenges[0].KeyHandle,
|
||||
Challenge: chals.U2FChallenges[0].Challenge,
|
||||
AppID: chals.U2FChallenges[0].AppID,
|
||||
Version: chals.U2FChallenges[0].Version,
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
u2fResBytes, err := json.Marshal(&u2f.AuthenticateChallengeResponse{
|
||||
KeyHandle: res.GetU2F().KeyHandle,
|
||||
SignatureData: res.GetU2F().Signature,
|
||||
ClientData: res.GetU2F().ClientData,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return u2fResBytes
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
@ -1255,24 +1211,6 @@ func TestDesktopAccessMFARequiresMfa(t *testing.T) {
|
|||
mfaHandler func(t *testing.T, ws *websocket.Conn, dev *auth.TestDevice)
|
||||
registerDevice func(t *testing.T, ctx context.Context, clt *auth.Client) *auth.TestDevice
|
||||
}{
|
||||
{
|
||||
name: "u2f",
|
||||
authPref: types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorU2F,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
},
|
||||
RequireSessionMFA: true,
|
||||
},
|
||||
mfaHandler: handleMFAU2FCChallenge,
|
||||
registerDevice: func(t *testing.T, ctx context.Context, clt *auth.Client) *auth.TestDevice {
|
||||
dev, err := auth.RegisterTestDevice(ctx, clt, "u2f", apiProto.DeviceType_DEVICE_TYPE_U2F, nil /* authenticator */)
|
||||
require.NoError(t, err)
|
||||
return dev
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webauthn",
|
||||
authPref: types.AuthPreferenceSpecV2{
|
||||
|
@ -1340,6 +1278,7 @@ func TestDesktopAccessMFARequiresMfa(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMFAWebauthnChallenge(t *testing.T, ws *websocket.Conn, dev *auth.TestDevice) {
|
||||
mfaChallange, err := tdp.DecodeMFAChallenge(bufio.NewReader(&WebsocketIO{Conn: ws}))
|
||||
require.NoError(t, err)
|
||||
|
@ -1358,33 +1297,6 @@ func handleMFAWebauthnChallenge(t *testing.T, ws *websocket.Conn, dev *auth.Test
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func handleMFAU2FCChallenge(t *testing.T, ws *websocket.Conn, dev *auth.TestDevice) {
|
||||
mfaChallange, err := tdp.DecodeMFAChallenge(bufio.NewReader(&WebsocketIO{Conn: ws}))
|
||||
require.NoError(t, err)
|
||||
res, err := dev.SolveAuthn(&apiProto.MFAAuthenticateChallenge{
|
||||
U2F: []*apiProto.U2FChallenge{{
|
||||
KeyHandle: mfaChallange.U2FChallenges[0].KeyHandle,
|
||||
Challenge: mfaChallange.U2FChallenges[0].Challenge,
|
||||
AppID: mfaChallange.U2FChallenges[0].AppID,
|
||||
Version: mfaChallange.U2FChallenges[0].Version,
|
||||
}},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = tdp.NewConn(&WebsocketIO{Conn: ws}).OutputMessage(tdp.MFA{
|
||||
Type: defaults.WebsocketU2FChallenge[0],
|
||||
MFAAuthenticateResponse: &authproto.MFAAuthenticateResponse{
|
||||
Response: &authproto.MFAAuthenticateResponse_U2F{
|
||||
U2F: &authproto.U2FResponse{
|
||||
KeyHandle: res.GetU2F().KeyHandle,
|
||||
ClientData: res.GetU2F().ClientData,
|
||||
Signature: res.GetU2F().Signature,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func (s *WebSuite) TestWebAgentForward(c *C) {
|
||||
ws, err := s.makeTerminal(s.authPack(c, "foo"))
|
||||
c.Assert(err, IsNil)
|
||||
|
@ -1734,64 +1646,6 @@ func (s *WebSuite) TestChangePasswordAndAddTOTPDeviceWithToken(c *C) {
|
|||
c.Assert(response.Created, IsNil)
|
||||
}
|
||||
|
||||
func (s *WebSuite) TestChangePasswordAndAddU2FDeviceWithToken(c *C) {
|
||||
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorU2F,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://" + s.server.ClusterName(),
|
||||
Facets: []string{"https://" + s.server.ClusterName()},
|
||||
},
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
err = s.server.Auth().SetAuthPreference(s.ctx, ap)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
s.createUser(c, "user2", "root", "password", "")
|
||||
|
||||
// create reset password token
|
||||
token, err := s.server.Auth().CreateResetPasswordToken(context.TODO(), auth.CreateUserTokenRequest{
|
||||
Name: "user2",
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
clt := s.client()
|
||||
re, err := clt.Get(context.Background(), clt.Endpoint("webapi", "u2f", "signuptokens", token.GetName()), url.Values{})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
var u2fRegReq u2f.RegisterChallenge
|
||||
c.Assert(json.Unmarshal(re.Bytes(), &u2fRegReq), IsNil)
|
||||
|
||||
u2fRegResp, err := s.mockU2F.RegisterResponse(&u2fRegReq)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
data, err := json.Marshal(auth.ChangePasswordWithTokenRequest{
|
||||
TokenID: token.GetName(),
|
||||
Password: []byte("qweQWE"),
|
||||
U2FRegisterResponse: u2fRegResp,
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
req, err := http.NewRequest("PUT", clt.Endpoint("webapi", "users", "password", "token"), bytes.NewBuffer(data))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
csrfToken := "2ebcb768d0090ea4368e42880c970b61865c326172a4a2343b645cf5d7f20992"
|
||||
addCSRFCookieToReq(req, csrfToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set(csrf.HeaderName, csrfToken)
|
||||
|
||||
re, err = clt.Client.RoundTrip(func() (*http.Response, error) {
|
||||
return clt.Client.HTTPClient().Do(req)
|
||||
})
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
// Test that no recovery codes are returned b/c cloud is not turned on.
|
||||
var response ui.RecoveryCodes
|
||||
c.Assert(json.Unmarshal(re.Bytes(), &response), IsNil)
|
||||
c.Assert(response.Codes, IsNil)
|
||||
c.Assert(response.Created, IsNil)
|
||||
}
|
||||
|
||||
// TestEmptyMotD ensures that responses returned by both /webapi/ping and
|
||||
// /webapi/motd work when no MotD is set
|
||||
func (s *WebSuite) TestEmptyMotD(c *C) {
|
||||
|
@ -2326,9 +2180,8 @@ func TestAddMFADevice(t *testing.T) {
|
|||
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOptional,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://localhost",
|
||||
Facets: []string{"https://localhost"},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: "localhost",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -2352,7 +2205,6 @@ func TestAddMFADevice(t *testing.T) {
|
|||
name string
|
||||
deviceName string
|
||||
getTOTPCode func() string
|
||||
getU2FResp func() *u2f.RegisterChallengeResponse
|
||||
getWebauthnResp func() *wanlib.CredentialCreationResponse
|
||||
}{
|
||||
{
|
||||
|
@ -2372,26 +2224,6 @@ func TestAddMFADevice(t *testing.T) {
|
|||
return regRes.GetTOTP().Code
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new U2F device",
|
||||
deviceName: "new-u2f",
|
||||
getU2FResp: func() *u2f.RegisterChallengeResponse {
|
||||
// Get u2f register challenge.
|
||||
res, err := env.server.Auth().CreateRegisterChallenge(ctx, &apiProto.CreateRegisterChallengeRequest{
|
||||
TokenID: privilegeToken,
|
||||
DeviceType: apiProto.DeviceType_DEVICE_TYPE_U2F,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, regRes, err := auth.NewTestDeviceFromChallenge(res)
|
||||
require.NoError(t, err)
|
||||
|
||||
return &u2f.RegisterChallengeResponse{
|
||||
RegistrationData: regRes.GetU2F().RegistrationData,
|
||||
ClientData: regRes.GetU2F().ClientData,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new Webauthn device",
|
||||
deviceName: "new-webauthn",
|
||||
|
@ -2416,15 +2248,11 @@ func TestAddMFADevice(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var totpCode string
|
||||
var u2fRegResp *u2f.RegisterChallengeResponse
|
||||
var webauthnRegResp *wanlib.CredentialCreationResponse
|
||||
|
||||
switch {
|
||||
case tc.getU2FResp != nil:
|
||||
u2fRegResp = tc.getU2FResp()
|
||||
case tc.getWebauthnResp != nil:
|
||||
if tc.getWebauthnResp != nil {
|
||||
webauthnRegResp = tc.getWebauthnResp()
|
||||
default:
|
||||
} else {
|
||||
totpCode = tc.getTOTPCode()
|
||||
}
|
||||
|
||||
|
@ -2434,7 +2262,6 @@ func TestAddMFADevice(t *testing.T) {
|
|||
PrivilegeTokenID: privilegeToken,
|
||||
DeviceName: tc.deviceName,
|
||||
SecondFactorToken: totpCode,
|
||||
U2FRegisterResponse: u2fRegResp,
|
||||
WebauthnRegisterResponse: webauthnRegResp,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -2472,9 +2299,8 @@ func TestGetAndDeleteMFADevices_WithRecoveryApprovedToken(t *testing.T) {
|
|||
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOptional,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://" + env.server.ClusterName(),
|
||||
Facets: []string{"https://" + env.server.ClusterName()},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: env.server.ClusterName(),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -2585,7 +2411,6 @@ func TestCreateAuthenticateChallenge(t *testing.T) {
|
|||
err = json.Unmarshal(res.Bytes(), &chal)
|
||||
require.NoError(t, err)
|
||||
require.True(t, chal.TOTPChallenge)
|
||||
require.Empty(t, chal.U2FChallenges)
|
||||
require.Empty(t, chal.WebauthnChallenge)
|
||||
})
|
||||
}
|
||||
|
@ -2602,9 +2427,8 @@ func TestCreateRegisterChallenge(t *testing.T) {
|
|||
ap, err := types.NewAuthPreference(types.AuthPreferenceSpecV2{
|
||||
Type: constants.Local,
|
||||
SecondFactor: constants.SecondFactorOn,
|
||||
U2F: &types.U2F{
|
||||
AppID: "https://" + env.server.ClusterName(),
|
||||
Facets: []string{"https://" + env.server.ClusterName()},
|
||||
Webauthn: &types.Webauthn{
|
||||
RPID: env.server.ClusterName(),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
@ -2623,10 +2447,6 @@ func TestCreateRegisterChallenge(t *testing.T) {
|
|||
name string
|
||||
deviceType string
|
||||
}{
|
||||
{
|
||||
name: "u2f challenge",
|
||||
deviceType: "u2f",
|
||||
},
|
||||
{
|
||||
name: "totp challenge",
|
||||
deviceType: "totp",
|
||||
|
@ -2651,8 +2471,6 @@ func TestCreateRegisterChallenge(t *testing.T) {
|
|||
require.NoError(t, json.Unmarshal(res.Bytes(), &chal))
|
||||
|
||||
switch tc.deviceType {
|
||||
case "u2f":
|
||||
require.NotNil(t, chal.U2F)
|
||||
case "totp":
|
||||
require.NotNil(t, chal.TOTP.QRCode)
|
||||
case "webauthn":
|
||||
|
@ -3820,8 +3638,7 @@ func validateTerminalStream(t *testing.T, conn *websocket.Conn) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type mockProxySettings struct {
|
||||
}
|
||||
type mockProxySettings struct{}
|
||||
|
||||
func (mock *mockProxySettings) GetProxySettings(ctx context.Context) (*webclient.ProxySettings, error) {
|
||||
return &webclient.ProxySettings{}, nil
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/httplib"
|
||||
|
@ -75,9 +74,7 @@ type addMFADeviceRequest struct {
|
|||
DeviceName string `json:"deviceName"`
|
||||
// SecondFactorToken is the totp code.
|
||||
SecondFactorToken string `json:"secondFactorToken"`
|
||||
// U2FRegisterResponse is U2F registration challenge response.
|
||||
U2FRegisterResponse *u2f.RegisterChallengeResponse `json:"u2fRegisterResponse"`
|
||||
// WebauthnRegisterResponse is U2F registration challenge response.
|
||||
// WebauthnRegisterResponse is a WebAuthn registration challenge response.
|
||||
WebauthnRegisterResponse *webauthn.CredentialCreationResponse `json:"webauthnRegisterResponse"`
|
||||
}
|
||||
|
||||
|
@ -98,13 +95,6 @@ func (h *Handler) addMFADeviceHandle(w http.ResponseWriter, r *http.Request, par
|
|||
protoReq.NewMFAResponse = &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_TOTP{
|
||||
TOTP: &proto.TOTPRegisterResponse{Code: req.SecondFactorToken},
|
||||
}}
|
||||
case req.U2FRegisterResponse != nil:
|
||||
protoReq.NewMFAResponse = &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_U2F{
|
||||
U2F: &proto.U2FRegisterResponse{
|
||||
RegistrationData: req.U2FRegisterResponse.RegistrationData,
|
||||
ClientData: req.U2FRegisterResponse.ClientData,
|
||||
},
|
||||
}}
|
||||
case req.WebauthnRegisterResponse != nil:
|
||||
protoReq.NewMFAResponse = &proto.MFARegisterResponse{Response: &proto.MFARegisterResponse_Webauthn{
|
||||
Webauthn: webauthn.CredentialCreationResponseToProto(req.WebauthnRegisterResponse),
|
||||
|
|
|
@ -75,10 +75,10 @@ type tdpMFACodec struct{}
|
|||
|
||||
func (tdpMFACodec) encode(chal *auth.MFAAuthenticateChallenge, envelopeType string) ([]byte, error) {
|
||||
switch envelopeType {
|
||||
case defaults.WebsocketWebauthnChallenge, defaults.WebsocketU2FChallenge:
|
||||
case defaults.WebsocketWebauthnChallenge:
|
||||
default:
|
||||
return nil, trace.BadParameter("received envelope type %v, expected either %v (WebAuthn) or %v (U2F)",
|
||||
envelopeType, defaults.WebsocketWebauthnChallenge, defaults.WebsocketU2FChallenge)
|
||||
return nil, trace.BadParameter(
|
||||
"received envelope type %v, expected %v (WebAuthn)", envelopeType, defaults.WebsocketWebauthnChallenge)
|
||||
}
|
||||
|
||||
tdpMsg := tdp.MFA{
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/httplib"
|
||||
|
@ -37,8 +36,6 @@ type changePasswordReq struct {
|
|||
NewPassword []byte `json:"new_password"`
|
||||
// SecondFactorToken is user 2nd factor token
|
||||
SecondFactorToken string `json:"second_factor_token"`
|
||||
// U2FSignResponse is U2F response
|
||||
U2FSignResponse *u2f.AuthenticateChallengeResponse `json:"u2f_sign_response"`
|
||||
// WebauthnAssertionResponse is a Webauthn response
|
||||
WebauthnAssertionResponse *wanlib.CredentialAssertionResponse `json:"webauthnAssertionResponse"`
|
||||
}
|
||||
|
@ -60,7 +57,6 @@ func (h *Handler) changePassword(w http.ResponseWriter, r *http.Request, p httpr
|
|||
OldPassword: req.OldPassword,
|
||||
NewPassword: req.NewPassword,
|
||||
SecondFactorToken: req.SecondFactorToken,
|
||||
U2FSignResponse: req.U2FSignResponse,
|
||||
WebauthnResponse: req.WebauthnAssertionResponse,
|
||||
}
|
||||
|
||||
|
|
|
@ -583,11 +583,6 @@ func (s *sessionCache) AuthenticateWebUser(req *client.AuthenticateWebUserReques
|
|||
authReq := auth.AuthenticateUserRequest{
|
||||
Username: req.User,
|
||||
}
|
||||
if req.U2FSignResponse != nil {
|
||||
authReq.U2F = &auth.U2FSignResponseCreds{
|
||||
SignResponse: *req.U2FSignResponse,
|
||||
}
|
||||
}
|
||||
if req.WebauthnAssertionResponse != nil {
|
||||
authReq.Webauthn = req.WebauthnAssertionResponse
|
||||
}
|
||||
|
@ -637,11 +632,6 @@ func (s *sessionCache) AuthenticateSSHUser(c client.AuthenticateSSHUserRequest)
|
|||
if c.Password != "" {
|
||||
authReq.Pass = &auth.PassCreds{Password: []byte(c.Password)}
|
||||
}
|
||||
if c.U2FSignResponse != nil {
|
||||
authReq.U2F = &auth.U2FSignResponseCreds{
|
||||
SignResponse: *c.U2FSignResponse,
|
||||
}
|
||||
}
|
||||
if c.WebauthnChallengeResponse != nil {
|
||||
authReq.Webauthn = c.WebauthnChallengeResponse
|
||||
}
|
||||
|
|
|
@ -36,7 +36,6 @@ import (
|
|||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/events"
|
||||
|
@ -350,33 +349,11 @@ func promptMFAChallenge(
|
|||
|
||||
// Convert from proto to JSON types.
|
||||
switch {
|
||||
// Webauthn takes precedence.
|
||||
case c.GetWebauthnChallenge() != nil:
|
||||
envelopeType = defaults.WebsocketWebauthnChallenge
|
||||
chal = &auth.MFAAuthenticateChallenge{
|
||||
WebauthnChallenge: wanlib.CredentialAssertionFromProto(c.WebauthnChallenge),
|
||||
}
|
||||
case len(c.U2F) > 0:
|
||||
u2fChals := make([]u2f.AuthenticateChallenge, 0, len(c.U2F))
|
||||
envelopeType = defaults.WebsocketU2FChallenge
|
||||
for _, uc := range c.U2F {
|
||||
u2fChals = append(u2fChals, u2f.AuthenticateChallenge{
|
||||
Version: uc.Version,
|
||||
Challenge: uc.Challenge,
|
||||
KeyHandle: uc.KeyHandle,
|
||||
AppID: uc.AppID,
|
||||
})
|
||||
}
|
||||
chal = &auth.MFAAuthenticateChallenge{
|
||||
AuthenticateChallenge: &u2f.AuthenticateChallenge{
|
||||
// Get the common challenge fields from the first item.
|
||||
// All of these fields should be identical for all u2fChals.
|
||||
Challenge: u2fChals[0].Challenge,
|
||||
AppID: u2fChals[0].AppID,
|
||||
Version: u2fChals[0].Version,
|
||||
},
|
||||
U2FChallenges: u2fChals,
|
||||
}
|
||||
default:
|
||||
return nil, trace.AccessDenied("only hardware keys are supported on the web terminal, please register a hardware device to connect to this server")
|
||||
}
|
||||
|
@ -545,7 +522,7 @@ func (t *TerminalHandler) writeError(err error, ws *websocket.Conn) error {
|
|||
// and port.
|
||||
func resolveServerHostPort(servername string, existingServers []types.Server) (string, int, error) {
|
||||
// If port is 0, client wants us to figure out which port to use.
|
||||
var defaultPort = 0
|
||||
defaultPort := 0
|
||||
|
||||
if servername == "" {
|
||||
return "", defaultPort, trace.BadParameter("empty server name")
|
||||
|
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/auth/u2f"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
"github.com/gravitational/teleport/lib/httplib"
|
||||
"github.com/gravitational/teleport/lib/web/ui"
|
||||
|
@ -167,8 +166,6 @@ func deleteUser(r *http.Request, params httprouter.Params, m userAPIGetter, user
|
|||
type privilegeTokenRequest struct {
|
||||
// SecondFactorToken is the totp code.
|
||||
SecondFactorToken string `json:"secondFactorToken"`
|
||||
// U2FSignResponse is u2f sign response for a u2f challenge.
|
||||
U2FSignResponse *u2f.AuthenticateChallengeResponse `json:"u2fSignResponse"`
|
||||
// WebauthnResponse is the response from authenticators.
|
||||
WebauthnResponse *wanlib.CredentialAssertionResponse `json:"webauthnAssertionResponse"`
|
||||
}
|
||||
|
@ -187,14 +184,6 @@ func (h *Handler) createPrivilegeTokenHandle(w http.ResponseWriter, r *http.Requ
|
|||
protoReq.ExistingMFAResponse = &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_TOTP{
|
||||
TOTP: &proto.TOTPResponse{Code: req.SecondFactorToken},
|
||||
}}
|
||||
case req.U2FSignResponse != nil:
|
||||
protoReq.ExistingMFAResponse = &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_U2F{
|
||||
U2F: &proto.U2FResponse{
|
||||
KeyHandle: req.U2FSignResponse.KeyHandle,
|
||||
ClientData: req.U2FSignResponse.ClientData,
|
||||
Signature: req.U2FSignResponse.SignatureData,
|
||||
},
|
||||
}}
|
||||
case req.WebauthnResponse != nil:
|
||||
protoReq.ExistingMFAResponse = &proto.MFAAuthenticateResponse{Response: &proto.MFAAuthenticateResponse_Webauthn{
|
||||
Webauthn: wanlib.CredentialAssertionResponseToProto(req.WebauthnResponse),
|
||||
|
|
Loading…
Reference in a new issue