github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/mfa/ceremony.go (about)

     1  /*
     2  Copyright 2024 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package mfa
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/gravitational/trace"
    23  
    24  	"github.com/gravitational/teleport/api/client/proto"
    25  	mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1"
    26  )
    27  
    28  // MFACeremonyClient is a client that can perform an MFA ceremony, from retrieving
    29  // the MFA challenge to prompting for an MFA response from the user.
    30  type MFACeremonyClient interface {
    31  	// CreateAuthenticateChallenge creates and returns MFA challenges for a users registered MFA devices.
    32  	CreateAuthenticateChallenge(ctx context.Context, in *proto.CreateAuthenticateChallengeRequest) (*proto.MFAAuthenticateChallenge, error)
    33  	// PromptMFA prompts the user for MFA.
    34  	PromptMFA(ctx context.Context, chal *proto.MFAAuthenticateChallenge, promptOpts ...PromptOpt) (*proto.MFAAuthenticateResponse, error)
    35  }
    36  
    37  // PerformMFACeremony retrieves an MFA challenge from the server with the given challenge extensions
    38  // and prompts the user to answer the challenge with the given promptOpts, and ultimately returning
    39  // an MFA challenge response for the user.
    40  func PerformMFACeremony(ctx context.Context, clt MFACeremonyClient, challengeRequest *proto.CreateAuthenticateChallengeRequest, promptOpts ...PromptOpt) (*proto.MFAAuthenticateResponse, error) {
    41  	if challengeRequest == nil {
    42  		return nil, trace.BadParameter("missing challenge request")
    43  	}
    44  
    45  	if challengeRequest.ChallengeExtensions == nil {
    46  		return nil, trace.BadParameter("missing challenge extensions")
    47  	}
    48  
    49  	if challengeRequest.ChallengeExtensions.Scope == mfav1.ChallengeScope_CHALLENGE_SCOPE_UNSPECIFIED {
    50  		return nil, trace.BadParameter("mfa challenge scope must be specified")
    51  	}
    52  
    53  	chal, err := clt.CreateAuthenticateChallenge(ctx, challengeRequest)
    54  	if err != nil {
    55  		// CreateAuthenticateChallenge returns a bad parameter error when the client
    56  		// user is not a Teleport user - for example, the AdminRole. Treat this as an MFA
    57  		// not supported error so the client knows when it can be ignored.
    58  		if trace.IsBadParameter(err) {
    59  			return nil, &ErrMFANotSupported
    60  		}
    61  		return nil, trace.Wrap(err)
    62  	}
    63  
    64  	// If an MFA required check was provided, and the client discovers MFA is not required,
    65  	// skip the MFA prompt and return an empty response.
    66  	if chal.MFARequired == proto.MFARequired_MFA_REQUIRED_NO {
    67  		return nil, &ErrMFANotRequired
    68  	}
    69  
    70  	return clt.PromptMFA(ctx, chal, promptOpts...)
    71  }
    72  
    73  type MFACeremony func(ctx context.Context, challengeRequest *proto.CreateAuthenticateChallengeRequest, promptOpts ...PromptOpt) (*proto.MFAAuthenticateResponse, error)
    74  
    75  // PerformAdminActionMFACeremony retrieves an MFA challenge from the server for an admin
    76  // action, prompts the user to answer the challenge, and returns the resulting MFA response.
    77  func PerformAdminActionMFACeremony(ctx context.Context, mfaCeremony MFACeremony, allowReuse bool) (*proto.MFAAuthenticateResponse, error) {
    78  	allowReuseExt := mfav1.ChallengeAllowReuse_CHALLENGE_ALLOW_REUSE_NO
    79  	if allowReuse {
    80  		allowReuseExt = mfav1.ChallengeAllowReuse_CHALLENGE_ALLOW_REUSE_YES
    81  	}
    82  
    83  	challengeRequest := &proto.CreateAuthenticateChallengeRequest{
    84  		Request: &proto.CreateAuthenticateChallengeRequest_ContextUser{},
    85  		MFARequiredCheck: &proto.IsMFARequiredRequest{
    86  			Target: &proto.IsMFARequiredRequest_AdminAction{
    87  				AdminAction: &proto.AdminAction{},
    88  			},
    89  		},
    90  		ChallengeExtensions: &mfav1.ChallengeExtensions{
    91  			Scope:      mfav1.ChallengeScope_CHALLENGE_SCOPE_ADMIN_ACTION,
    92  			AllowReuse: allowReuseExt,
    93  		},
    94  	}
    95  
    96  	return mfaCeremony(ctx, challengeRequest, WithPromptReasonAdminAction())
    97  }