Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,052 changes: 1,868 additions & 1,184 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

639 changes: 607 additions & 32 deletions api/gen/proto/go/teleport/mfa/v1/challenge.pb.go

Large diffs are not rendered by default.

546 changes: 492 additions & 54 deletions api/gen/proto/go/teleport/mfa/v1/service.pb.go

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions api/gen/proto/go/teleport/mfa/v1/service_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,8 @@ message MFAAuthenticateChallenge {
// IdP redirect URL to perform an MFA check in the IdP and obtain an MFA token.
// This token paired with the request id can then be used as MFA verification.
SSOChallenge SSOChallenge = 5;
// Browser Challenge is an MFA challenge that the user solves in the browser.
BrowserMFAChallenge BrowserMFAChallenge = 6;
}

// MFAAuthenticateResponse is a response to MFAAuthenticateChallenge using one
Expand All @@ -1312,6 +1314,7 @@ message MFAAuthenticateResponse {
TOTPResponse TOTP = 2;
webauthn.CredentialAssertionResponse Webauthn = 3;
SSOResponse SSO = 4;
BrowserMFAResponse Browser = 5;
}
}

Expand Down Expand Up @@ -1346,6 +1349,20 @@ message SSOResponse {
string token = 2;
}

// BrowserMFAChallenge contains browser MFA request details to perform a browser MFA check.
message BrowserMFAChallenge {
// RequestId is the ID of a browser MFA request.
string request_id = 1;
}

// BrowserMFAResponse is a response to BrowserMFAChallenge.
message BrowserMFAResponse {
// RequestId is the ID of a browser MFA request.
string request_id = 1;
// WebauthnResponse is the WebAuthn credential assertion response from the browser MFA flow.
webauthn.CredentialAssertionResponse webauthn_response = 2;
}

// MFARegisterChallenge is a challenge for registering a new MFA device.
message MFARegisterChallenge {
// Request depends on the type of the MFA device being registered.
Expand Down Expand Up @@ -2081,6 +2098,13 @@ message CreateAuthenticateChallengeRequest {
// When using SSO MFA, this address is required to determine which URL to redirect the
// user to when there are multiple options.
string ProxyAddress = 8 [(gogoproto.jsontag) = "proxy_address,omitempty"];
// BrowserMFATSHRedirectURL should be supplied if the client supports Browser MFA checks.
// This is used by tsh to provide the tsh callback URL for browser MFA flows.
string BrowserMFATSHRedirectURL = 9 [(gogoproto.jsontag) = "browser_mfa_tsh_redirect_url,omitempty"];
// BrowserMFARequestID should be supplied when the browser is generating an MFA challenge
// for browser MFA. The auth server will look up the SSOMFASession with this ID to retrieve
// the challenge extensions and validate the requesting user matches the session owner.
string BrowserMFARequestID = 10 [(gogoproto.jsontag) = "browser_mfa_request_id,omitempty"];
}

// CreatePrivilegeTokenRequest defines a request to obtain a privilege token.
Expand Down
10 changes: 10 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2733,6 +2733,16 @@ message AuthPreferenceSpecV2 {
// StableUnixUserConfig contains the cluster-wide configuration for stable
// UNIX users.
StableUNIXUserConfig stable_unix_user_config = 22;

// AllowBrowserAuthentication enables/disables browser-based authentication for
// authenticating CLI sessions.
// When set to false, authentication flows that require a browser will be disabled.
// Defaults to true.
BoolValue AllowBrowserAuthentication = 23 [
(gogoproto.nullable) = true,
(gogoproto.jsontag) = "allow_browser_authentication,omitempty",
(gogoproto.customtype) = "BoolOption"
];
}

// StableUNIXUserConfig contains the cluster-wide configuration for stable UNIX
Expand Down
19 changes: 19 additions & 0 deletions api/proto/teleport/mfa/v1/challenge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ message AuthenticateChallenge {
// SSO MFA challenge. If set, the client can go to the IdP redirect URL to perform an MFA check and obtain an MFA
// token. This token paired with the request id can be used for verification.
SSOChallenge sso_challenge = 3;
// Browser challenge allows a user to MFA in the browser, to get a WebAuthn
// response that is returned to the client to be used for verification.
BrowserMFAChallenge browser_challenge = 4;
}

// AuthenticateResponse is a response to AuthenticateChallenge using one of the MFA devices registered for a user.
Expand All @@ -45,6 +48,8 @@ message AuthenticateResponse {
webauthn.CredentialAssertionResponse webauthn = 2;
// Response to an SSO challenge.
SSOChallengeResponse sso = 3;
// Response to a browser challenge.
BrowserMFAResponse browser = 4;
}
}

Expand All @@ -65,3 +70,17 @@ message SSOChallengeResponse {
// Secret token used to verify the user's SSO MFA session.
string token = 2;
}

// BrowserMFAChallenge contains browser MFA request details to perform a browser MFA check.
message BrowserMFAChallenge {
// RequestId is the ID of a browser MFA request.
string request_id = 1;
}

// BrowserMFAResponse is a response to BrowserMFAChallenge.
message BrowserMFAResponse {
// RequestId is the ID of a browser MFA request.
string request_id = 1;
// WebauthnResponse is the WebAuthn credential assertion response from the browser MFA flow.
webauthn.CredentialAssertionResponse webauthn_response = 2;
}
26 changes: 26 additions & 0 deletions api/proto/teleport/mfa/v1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ service MFAService {
// The payload is used to verify the challenge is tied to the correct user session. If the verification fails, an
// error is returned.
rpc VerifyValidatedMFAChallenge(VerifyValidatedMFAChallengeRequest) returns (VerifyValidatedMFAChallengeResponse);
// CompleteBrowserMFAChallenge completes a browser MFA challenge request by encrypting
// it and returning it to the browser.
// This is called when a user has been sent to the browser to solve an MFA challenge
// that was triggered by tsh or tctl. When the user solves the MFA challenge, the
// response is sent to this RPC. CompleteBrowserMFAChallenge receives the MFA
// response, encrypts it, appends it to tsh/tctl's callback URL and returns it to the browser.
// More info: https://github.com/gravitational/teleport/blob/master/rfd/0233-tsh-browser-mfa.md
rpc CompleteBrowserMFAChallenge(CompleteBrowserMFAChallengeRequest) returns (CompleteBrowserMFAChallengeResponse);
}

// CreateSessionChallengeRequest is the request message for CreateSessionChallenge.
Expand All @@ -61,6 +69,10 @@ message CreateSessionChallengeRequest {
// Proxy address the user is using to connect to the Proxy. Required for SSO MFA to determine which URL to redirect
// the user to when there are multiple options.
string proxy_address_for_sso = 4;
// Used to construct the redirect URL for browser-based MFA flows. If the client supports browser MFA, this field
// should be set to the URL where the browser should redirect to tsh after completing the MFA challenge.
Comment thread
codingllama marked this conversation as resolved.
// Format: http://127.0.0.1:[random_port]/callback?response={encrypted_webauthn_response}
string browser_mfa_tsh_redirect_url = 5;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will this URL be validated by the auth server?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, this will also be validated by ValidateClientRedirect on the server side

}

// CreateSessionChallengeResponse is the response message for CreateSessionChallenge.
Expand Down Expand Up @@ -148,3 +160,17 @@ message VerifyValidatedMFAChallengeRequest {

// VerifyValidatedMFAChallengeResponse is the response message for VerifyValidatedMFAChallenge.
message VerifyValidatedMFAChallengeResponse {}

// CompleteBrowserMFAChallengeRequest is used to complete an MFA response
// during a browser-based MFA authentication flow.
message CompleteBrowserMFAChallengeRequest {
BrowserMFAResponse browser_mfa_response = 1;
}

// CompleteBrowserMFAChallengeResponse contains the redirect URL to send
// the user back to after successfully completing browser-based MFA authentication.
message CompleteBrowserMFAChallengeResponse {
// tsh_redirect_url is the callback URL to tsh's local HTTP server with the encrypted WebAuthn response.
// Format: http://127.0.0.1:[random_port]/callback?response={encrypted_webauthn_response}
string tsh_redirect_url = 1;
}
26 changes: 26 additions & 0 deletions api/types/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ type AuthPreference interface {
// SetAllowHeadless sets the value of the allow headless setting.
SetAllowHeadless(b bool)
Comment thread
codingllama marked this conversation as resolved.

// GetAllowBrowserAuthentication returns if browser authentication is allowed by cluster settings.
GetAllowBrowserAuthentication() bool
// SetAllowBrowserAuthentication sets the value of the allow browser authentication setting.
SetAllowBrowserAuthentication(b bool)

// SetRequireMFAType sets the type of MFA requirement enforced for this cluster.
SetRequireMFAType(RequireMFAType)
// GetRequireMFAType returns the type of MFA requirement enforced for this cluster.
Expand Down Expand Up @@ -467,6 +472,22 @@ func (c *AuthPreferenceV2) SetAllowHeadless(b bool) {
c.Spec.AllowHeadless = NewBoolOption(b)
}

// GetAllowBrowserAuthentication returns whether browser authentication is
// allowed. If it's not explicitly configured, it defaults to true when
// WebAuthn is enabled as a second factor.
func (c *AuthPreferenceV2) GetAllowBrowserAuthentication() bool {
if c.Spec.AllowBrowserAuthentication != nil {
return c.Spec.AllowBrowserAuthentication.Value
}

// Default to enabled when WebAuthn is enabled.
return c.IsSecondFactorWebauthnAllowed()
}

func (c *AuthPreferenceV2) SetAllowBrowserAuthentication(b bool) {
c.Spec.AllowBrowserAuthentication = NewBoolOption(b)
}

// SetRequireMFAType sets the type of MFA requirement enforced for this cluster.
func (c *AuthPreferenceV2) SetRequireMFAType(t RequireMFAType) {
c.Spec.RequireMFAType = t
Expand Down Expand Up @@ -809,6 +830,11 @@ func (c *AuthPreferenceV2) CheckAndSetDefaults() error {
return trace.BadParameter("missing required Webauthn configuration for headless=true")
}

// Validate AllowBrowserAuthentication. WebAuthn is required for browser authentication.
if !hasWebauthn && c.Spec.AllowBrowserAuthentication != nil && c.Spec.AllowBrowserAuthentication.Value {
return trace.BadParameter("missing required Webauthn configuration for allow_browser_authentication=true")
}

// Prevent local lockout by disabling local second factor methods.
if c.GetAllowLocalAuth() && c.IsSecondFactorEnforced() && !c.IsSecondFactorLocalAllowed() {
if c.IsSecondFactorSSOAllowed() {
Expand Down
77 changes: 77 additions & 0 deletions api/types/authentication_authpreference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,83 @@ func TestAuthPreferenceV2_CheckAndSetDefaults_secondFactor(t *testing.T) {
},
wantErr: "invalid local connector",
},
// AllowBrowserAuthentication
{
name: "OK AllowBrowserAuthentication defaults to false without Webauthn",
secondFactors: []constants.SecondFactorType{
constants.SecondFactorOff,
constants.SecondFactorOTP,
},
spec: types.AuthPreferenceSpecV2{
Type: constants.Local,
AllowBrowserAuthentication: nil, // aka unset
},
assertFn: func(t *testing.T, cap *types.AuthPreferenceV2) {
assert.False(t, cap.GetAllowBrowserAuthentication(), "AllowBrowserAuthentication")
},
},
{
name: "OK AllowBrowserAuthentication=false without Webauthn",
secondFactors: []constants.SecondFactorType{
constants.SecondFactorOff,
constants.SecondFactorOTP,
},
spec: types.AuthPreferenceSpecV2{
Type: constants.Local,
AllowBrowserAuthentication: types.NewBoolOption(false),
},
assertFn: func(t *testing.T, cap *types.AuthPreferenceV2) {
assert.False(t, cap.GetAllowBrowserAuthentication(), "AllowBrowserAuthentication")
},
},
{
name: "NOK AllowBrowserAuthentication=true without Webauthn",
secondFactors: []constants.SecondFactorType{
constants.SecondFactorOff,
constants.SecondFactorOTP,
},
spec: types.AuthPreferenceSpecV2{
Type: constants.Local,
AllowBrowserAuthentication: types.NewBoolOption(true),
},
wantErr: "required Webauthn",
},
{
name: "OK AllowBrowserAuthentication defaults to true with Webauthn",
secondFactors: secondFactorWebActive,
spec: types.AuthPreferenceSpecV2{
Type: constants.Local,
Webauthn: minimalWeb,
AllowBrowserAuthentication: nil, // aka unset
},
assertFn: func(t *testing.T, cap *types.AuthPreferenceV2) {
assert.True(t, cap.GetAllowBrowserAuthentication(), "AllowBrowserAuthentication")
},
},
{
name: "OK AllowBrowserAuthentication=false with Webauthn",
secondFactors: secondFactorWebActive,
spec: types.AuthPreferenceSpecV2{
Type: constants.Local,
Webauthn: minimalWeb,
AllowBrowserAuthentication: types.NewBoolOption(false),
},
assertFn: func(t *testing.T, cap *types.AuthPreferenceV2) {
assert.False(t, cap.GetAllowBrowserAuthentication(), "AllowBrowserAuthentication")
},
},
{
name: "OK AllowBrowserAuthentication=true with Webauthn",
secondFactors: secondFactorWebActive,
spec: types.AuthPreferenceSpecV2{
Type: constants.Local,
Webauthn: minimalWeb,
AllowBrowserAuthentication: types.NewBoolOption(true),
},
assertFn: func(t *testing.T, cap *types.AuthPreferenceV2) {
assert.True(t, cap.GetAllowBrowserAuthentication(), "AllowBrowserAuthentication")
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down
Loading
Loading