Skip to content
135 changes: 94 additions & 41 deletions api/gen/proto/go/teleport/mfa/v1/service.pb.go

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

3 changes: 3 additions & 0 deletions api/proto/teleport/mfa/v1/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ message ReplicateValidatedMFAChallengeRequest {
// Name of the target cluster where the SSH session is being established. Required in order to match the validated
// challenge to the correct session.
string target_cluster = 4;
// Username of the Teleport user for whom the challenge was issued. This should be the Teleport username (not the SSH
// login name) and must correspond to a user in the cluster specified by source_cluster.
string username = 5;
}

// ReplicateValidatedMFAChallengeResponse is the response message for ReplicateValidatedMFAChallenge.
Expand Down
116 changes: 100 additions & 16 deletions lib/auth/mfa/mfav1/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,61 @@ func (s *Service) ValidateSessionChallenge(
return &mfav1.ValidateSessionChallengeResponse{}, nil
}

// ReplicateValidatedMFAChallenge implements the mfav1.MFAServiceServer.ReplicateValidatedMFAChallenge method.
func (s *Service) ReplicateValidatedMFAChallenge(
ctx context.Context,
req *mfav1.ReplicateValidatedMFAChallengeRequest,
) (*mfav1.ReplicateValidatedMFAChallengeResponse, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if !isRemoteProxy(*authCtx) {
return nil, trace.AccessDenied("only remote proxy identities can replicate validated MFA challenges")
}

if err := checkReplicateValidatedMFAChallengeRequest(req); err != nil {
return nil, trace.Wrap(err)
}

currentCluster, err := s.cache.GetClusterName(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

if req.GetTargetCluster() != currentCluster.GetClusterName() {
return nil,
trace.BadParameter(
"target cluster %q does not match current cluster %q",
req.GetTargetCluster(),
currentCluster.GetClusterName(),
)
}

chal := &mfav1.ValidatedMFAChallenge{
Kind: types.KindValidatedMFAChallenge,
Version: "v1",
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace with constant?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't know we had a constant for that. Thank you!

Replaced in d2e6c8e

Metadata: &types.Metadata{
Name: req.GetName(),
},
Spec: &mfav1.ValidatedMFAChallengeSpec{
Payload: req.GetPayload(),
SourceCluster: req.GetSourceCluster(),
TargetCluster: req.GetTargetCluster(),
},
}

created, err := s.storage.CreateValidatedMFAChallenge(ctx, req.GetUsername(), chal)
if err != nil {
return nil, trace.Wrap(err)
}

return &mfav1.ReplicateValidatedMFAChallengeResponse{
ReplicatedChallenge: created,
}, nil
}

func (s *Service) VerifyValidatedMFAChallenge(
ctx context.Context,
req *mfav1.VerifyValidatedMFAChallengeRequest,
Expand Down Expand Up @@ -429,13 +484,8 @@ func (s *Service) VerifyValidatedMFAChallenge(
}

func validateCreateSessionChallengeRequest(req *mfav1.CreateSessionChallengeRequest) error {
payload := req.GetPayload()
if payload == nil {
return trace.BadParameter("missing CreateSessionChallengeRequest payload")
}

if len(payload.GetSshSessionId()) == 0 {
return trace.BadParameter("empty SshSessionId in payload")
if err := checkPayload(req.GetPayload()); err != nil {
return trace.Wrap(err)
}

// If either SSO challenge field is set, both must be set.
Expand Down Expand Up @@ -657,35 +707,69 @@ func mfaPreferences(pref types.AuthPreference) (*types.U2F, *types.Webauthn, err
return u2f, webauthn, nil
}

func checkVerifyValidatedMFAChallengeRequest(req *mfav1.VerifyValidatedMFAChallengeRequest) error {
func checkReplicateValidatedMFAChallengeRequest(req *mfav1.ReplicateValidatedMFAChallengeRequest) error {
switch {
case req == nil:
return trace.BadParameter("param VerifyValidatedMFAChallengeRequest is nil")
case req.GetName() == "":
return trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest name")

case req.GetSourceCluster() == "":
return trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest source_cluster")

case req.GetTargetCluster() == "":
return trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest target_cluster")

case req.GetUsername() == "":
return trace.BadParameter("missing ReplicateValidatedMFAChallengeRequest username")
}

if err := checkPayload(req.GetPayload()); err != nil {
return trace.Wrap(err)
}

return nil
}

func checkVerifyValidatedMFAChallengeRequest(req *mfav1.VerifyValidatedMFAChallengeRequest) error {
switch {
case req.GetUsername() == "":
return trace.BadParameter("missing VerifyValidatedMFAChallengeRequest username")

case req.GetName() == "":
return trace.BadParameter("missing VerifyValidatedMFAChallengeRequest name")
}

payload := req.GetPayload()
if payload == nil {
return trace.BadParameter("missing VerifyValidatedMFAChallengeRequest payload")
if err := checkPayload(req.GetPayload()); err != nil {
return trace.Wrap(err)
}

switch p := payload.GetPayload().(type) {
return nil
}

func checkPayload(sip *mfav1.SessionIdentifyingPayload) error {
switch payload := sip.GetPayload().(type) {
case *mfav1.SessionIdentifyingPayload_SshSessionId:
if len(p.SshSessionId) == 0 {
if len(payload.SshSessionId) == 0 {
return trace.BadParameter("empty SshSessionId in payload")
}

case nil:
return trace.NotImplemented("missing or unsupported SessionIdentifyingPayload in request")

default:
return trace.BadParameter("unexpected SessionIdentifyingPayload type %T (this is a bug)", p)
return trace.BadParameter("unexpected SessionIdentifyingPayload type %T (this is a bug)", payload)
}

return nil
}

func isRemoteProxy(authContext authz.Context) bool {
if _, ok := authContext.UnmappedIdentity.(authz.RemoteBuiltinRole); !ok {
return false
}

if !authContext.Checker.HasRole(string(types.RoleRemoteProxy)) {
return false
}

return true
}
Loading
Loading