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 }