Skip to content

Commit

Permalink
Always issue relogin and per-session mfa requests for the root cluste…
Browse files Browse the repository at this point in the history
…r URI (#42355)

* Always issue per-session MFA requests for a root cluster URI

* Always issue relogin requests for a root cluster URI

* Check if cluster URI is root cluster URI in login handlers

* Pass clusterUri instead of rootClusterUri to the ReAuthenticate dialog

* Reserve `root_cluster_uri` field too

* Resolve conflicts

(cherry picked from commit 282f6f0)
  • Loading branch information
gzdunek committed Jun 7, 2024
1 parent e64a6eb commit 600ec79
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 139 deletions.
191 changes: 96 additions & 95 deletions gen/proto/go/teleport/lib/teleterm/v1/tshd_events_service.pb.go

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions gen/proto/ts/teleport/lib/teleterm/v1/tshd_events_service_pb.ts

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

10 changes: 10 additions & 0 deletions lib/teleterm/apiserver/handler/handler_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ func (s *Handler) Login(ctx context.Context, req *api.LoginRequest) (*api.EmptyR
if err != nil {
return nil, trace.Wrap(err)
}

if !cluster.URI.IsRoot() {
return nil, trace.BadParameter("cluster URI must be a root URI")
}

// The credentials + MFA login flow in the Electron app assumes that the default CLI prompt is
// used and works around that. Thus we have to remove the teleterm-specific MFAPromptConstructor
// added by daemon.Service.ResolveClusterURI.
Expand Down Expand Up @@ -83,6 +88,11 @@ func (s *Handler) LoginPasswordless(stream api.TerminalService_LoginPasswordless
if err != nil {
return trace.Wrap(err)
}

if !cluster.URI.IsRoot() {
return trace.BadParameter("cluster URI must be a root URI")
}

// The passwordless login flow in the Electron app assumes that the default CLI prompt is used and
// works around that. Thus we have to remove the teleterm-specific MFAPromptConstructor added by
// daemon.Service.ResolveClusterURI.
Expand Down
6 changes: 3 additions & 3 deletions lib/teleterm/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func (s *Service) ResolveClusterURI(uri uri.ResourceURI) (*clusters.Cluster, *cl

// Custom MFAPromptConstructor gets removed during the calls to Login and LoginPasswordless RPCs.
// Those RPCs assume that the default CLI prompt is in use.
clusterClient.MFAPromptConstructor = s.NewMFAPromptConstructor(cluster.URI.String())
clusterClient.MFAPromptConstructor = s.NewMFAPromptConstructor(cluster.URI)
return cluster, clusterClient, nil
}

Expand Down Expand Up @@ -346,7 +346,7 @@ func (s *Service) createGateway(ctx context.Context, params CreateGatewayParams)
LocalPort: params.LocalPort,
OnExpiredCert: s.reissueGatewayCerts,
KubeconfigsDir: s.cfg.KubeconfigsDir,
MFAPromptConstructor: s.NewMFAPromptConstructor(targetURI.String()),
MFAPromptConstructor: s.NewMFAPromptConstructor(targetURI),
ClusterClient: clusterClient,
}

Expand All @@ -370,7 +370,7 @@ func (s *Service) createGateway(ctx context.Context, params CreateGatewayParams)
// per-session MFA checks.
func (s *Service) reissueGatewayCerts(ctx context.Context, g gateway.Gateway) (tls.Certificate, error) {
reloginReq := &api.ReloginRequest{
RootClusterUri: g.TargetURI().GetClusterURI().String(),
RootClusterUri: g.TargetURI().GetRootClusterURI().String(),
Reason: &api.ReloginRequest_GatewayCertExpired{
GatewayCertExpired: &api.GatewayCertExpired{
GatewayUri: g.URI().String(),
Expand Down
23 changes: 12 additions & 11 deletions lib/teleterm/daemon/mfaprompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,30 @@ import (
wancli "github.com/gravitational/teleport/lib/auth/webauthncli"
wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
libmfa "github.com/gravitational/teleport/lib/client/mfa"
"github.com/gravitational/teleport/lib/teleterm/api/uri"
)

// mfaPrompt is a tshd implementation of mfa.Prompt that uses the
// tshdEventsClient to propagate mfa prompts to the Electron App.
type mfaPrompt struct {
cfg libmfa.PromptConfig
clusterURI string
resourceURI uri.ResourceURI
promptAppMFA func(ctx context.Context, in *api.PromptMFARequest) (*api.PromptMFAResponse, error)
}

// NewMFAPromptConstructor returns a new MFA prompt constructor
// for this service and the given cluster.
func (s *Service) NewMFAPromptConstructor(clusterURI string) func(cfg *libmfa.PromptConfig) mfa.Prompt {
// for this service and the given resource URI.
func (s *Service) NewMFAPromptConstructor(resourceURI uri.ResourceURI) func(cfg *libmfa.PromptConfig) mfa.Prompt {
return func(cfg *libmfa.PromptConfig) mfa.Prompt {
return s.NewMFAPrompt(clusterURI, cfg)
return s.NewMFAPrompt(resourceURI, cfg)
}
}

// NewMFAPrompt returns a new MFA prompt for this service and the given cluster.
func (s *Service) NewMFAPrompt(clusterURI string, cfg *libmfa.PromptConfig) *mfaPrompt {
// NewMFAPrompt returns a new MFA prompt for this service and the given resource URI.
func (s *Service) NewMFAPrompt(resourceURI uri.ResourceURI, cfg *libmfa.PromptConfig) *mfaPrompt {
return &mfaPrompt{
cfg: *cfg,
clusterURI: clusterURI,
resourceURI: resourceURI,
promptAppMFA: s.promptAppMFA,
}
}
Expand Down Expand Up @@ -125,10 +126,10 @@ func (p *mfaPrompt) promptWebauthn(ctx context.Context, chal *proto.MFAAuthentic

func (p *mfaPrompt) promptMFA(ctx context.Context, chal *proto.MFAAuthenticateChallenge, runOpts libmfa.RunOpts) (*proto.MFAAuthenticateResponse, error) {
resp, err := p.promptAppMFA(ctx, &api.PromptMFARequest{
RootClusterUri: p.clusterURI,
Reason: p.cfg.PromptReason,
Totp: runOpts.PromptTOTP,
Webauthn: runOpts.PromptWebauthn,
ClusterUri: p.resourceURI.GetClusterURI().String(),
Reason: p.cfg.PromptReason,
Totp: runOpts.PromptTOTP,
Webauthn: runOpts.PromptWebauthn,
})
if err != nil {
return nil, trail.FromGRPC(err)
Expand Down
4 changes: 3 additions & 1 deletion proto/teleport/lib/teleterm/v1/tshd_events_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,12 @@ message SendPendingHeadlessAuthenticationResponse {}

// Request for PromptMFA.
message PromptMFARequest {
string root_cluster_uri = 1;
reserved 1; // root_cluster_uri
reserved "root_cluster_uri";
string reason = 2;
bool totp = 3;
bool webauthn = 4;
string cluster_uri = 5;
}

// Response for PromptMFA.
Expand Down
4 changes: 0 additions & 4 deletions web/packages/teleterm/src/services/tshdEvents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ export interface ReloginRequest extends api.ReloginRequest {
}
export type SendNotificationRequest = api.SendNotificationRequest;

export type PromptMfaRequest = api.PromptMFARequest & {
rootClusterUri: uri.RootClusterUri;
};

export interface SendPendingHeadlessAuthenticationRequest
extends api.SendPendingHeadlessAuthenticationRequest {
rootClusterUri: uri.RootClusterUri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { makeRootCluster } from 'teleterm/services/tshd/testHelpers';
import {
makeRootCluster,
makeLeafCluster,
} from 'teleterm/services/tshd/testHelpers';
import { MockAppContextProvider } from 'teleterm/ui/fixtures/MockAppContextProvider';

import { ReAuthenticate } from './ReAuthenticate';
Expand All @@ -27,7 +30,7 @@ export default {

const promptMfaRequest = {
reason: 'MFA is required to access Kubernetes cluster "minikube"',
rootClusterUri: makeRootCluster().uri,
clusterUri: makeRootCluster().uri,
webauthn: false,
totp: false,
};
Expand Down Expand Up @@ -76,7 +79,22 @@ export const MultilineTitle = () => (
...promptMfaRequest,
webauthn: true,
totp: true,
rootClusterUri: '/clusters/lorem.cloud.gravitational.io',
clusterUri: '/clusters/lorem.cloud.gravitational.io',
}}
onCancel={() => {}}
onSuccess={showToken}
/>
</MockAppContextProvider>
);

export const ForLeafCluster = () => (
<MockAppContextProvider>
<ReAuthenticate
promptMfaRequest={{
...promptMfaRequest,
webauthn: true,
totp: true,
clusterUri: makeLeafCluster().uri,
}}
onCancel={() => {}}
onSuccess={showToken}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
*/

import { FC, useState } from 'react';

import { PromptMFARequest } from 'gen-proto-ts/teleport/lib/teleterm/v1/tshd_events_service_pb';

import DialogConfirmation, {
DialogContent,
DialogFooter,
Expand All @@ -42,7 +45,6 @@ import { Option } from 'shared/components/Select';
import { assertUnreachable } from 'shared/utils/assertUnreachable';

import { useAppContext } from 'teleterm/ui/appContextProvider';
import { PromptMfaRequest } from 'teleterm/services/tshdEvents';
import LinearProgress from 'teleterm/ui/components/LinearProgress';
import svgHardwareKey from 'teleterm/ui/ClusterConnect/ClusterLogin/FormLogin/PromptWebauthn/hardware.svg';
import { useLogger } from 'teleterm/ui/hooks/useLogger';
Expand All @@ -51,7 +53,7 @@ import { routing } from 'teleterm/ui/uri';
type MfaType = 'webauthn' | 'totp';

export const ReAuthenticate: FC<{
promptMfaRequest: PromptMfaRequest;
promptMfaRequest: PromptMFARequest;
onCancel: () => void;
onSuccess: (otp: string) => void;
}> = props => {
Expand Down Expand Up @@ -83,13 +85,18 @@ export const ReAuthenticate: FC<{
const [selectedMfaType, setSelectedMfaType] = useState(availableMfaTypes[0]);
const [otpToken, setOtpToken] = useState('');

const { rootClusterUri } = req;
const { clusterUri } = req;
const { clustersService } = useAppContext();
// TODO(ravicious): Use a profile name here from the URI and remove the dependency on
// clustersService. https://github.com/gravitational/teleport/issues/33733
const clusterName =
clustersService.findCluster(rootClusterUri)?.name ||
const rootClusterUri = routing.ensureRootClusterUri(clusterUri);
const rootClusterName =
clustersService.findRootClusterByResource(rootClusterUri)?.name ||
routing.parseClusterName(rootClusterUri);
const clusterName =
clustersService.findCluster(clusterUri)?.name ||
routing.parseClusterName(clusterUri);
const isLeafCluster = routing.isLeafCluster(clusterUri);

return (
<DialogConfirmation
Expand All @@ -114,7 +121,7 @@ export const ReAuthenticate: FC<{
alignItems="baseline"
>
<Text typography="h4">
Verify your identity on <strong>{clusterName}</strong>
Verify your identity on <strong>{rootClusterName}</strong>
</Text>
<ButtonIcon
type="button"
Expand All @@ -129,6 +136,7 @@ export const ReAuthenticate: FC<{
<Flex flexDirection="column" gap={4} alignItems="flex-start">
<Text typography="body1" color="text.slightlyMuted">
{req.reason}
{isLeafCluster && ` from trusted cluster "${clusterName}"`}
</Text>

<Flex width="100%" gap={3} flex-wrap="no-wrap">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import * as types from 'teleterm/services/tshd/types';
import { RootClusterUri } from 'teleterm/ui/uri';
import { ResourceSearchError } from 'teleterm/ui/services/resources';

import { PromptMfaRequest } from 'teleterm/services/tshdEvents';

import { ImmutableStore } from '../immutableStore';

import type * as uri from 'teleterm/ui/uri';
Expand Down Expand Up @@ -211,7 +209,7 @@ export interface DialogHeadlessAuthentication {

export interface DialogReAuthenticate {
kind: 'reauthenticate';
promptMfaRequest: PromptMfaRequest;
promptMfaRequest: tshdEventsApi.PromptMFARequest;
onSuccess(totpCode: string): void;
onCancel(): void;
}
Expand Down

0 comments on commit 600ec79

Please sign in to comment.