diff --git a/lib/auth/join.go b/lib/auth/join.go index 283c9acdb14f7..0f3480e8fb9ac 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -48,17 +48,6 @@ import ( "github.com/gravitational/teleport/lib/events" ) -// tokenJoinMethod returns the join method of the token with the given tokenName -func (a *Server) tokenJoinMethod(ctx context.Context, tokenName string) types.JoinMethod { - provisionToken, err := a.GetToken(ctx, tokenName) - if err != nil { - // could not find dynamic token, assume static token. If it does not - // exist this will be caught later. - return types.JoinMethodToken - } - return provisionToken.GetJoinMethod() -} - // checkTokenJoinRequestCommon checks all token join rules that are common to // all join methods, including token existence, token TTL, and allowed roles. func (a *Server) checkTokenJoinRequestCommon(ctx context.Context, req *types.RegisterUsingTokenRequest) (types.ProvisionToken, error) { @@ -229,19 +218,31 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } - method := a.tokenJoinMethod(ctx, req.Token) - switch method { + // perform common token checks + provisionToken, err = a.checkTokenJoinRequestCommon(ctx, req) + if err != nil { + return nil, trace.Wrap(err) + } + // nb: token returned by checkTokenJoinRequestCommon may be a + // ProvisionTokenV2 derived from a static token. You cannot assume you will + // be able to fetch this same token directly from the backend. + method := provisionToken.GetJoinMethod() + + // Call join method-specific validation + switch provisionToken.GetJoinMethod() { case types.JoinMethodEC2: if err := a.checkEC2JoinRequest(ctx, req); err != nil { return nil, trace.Wrap(err) } case types.JoinMethodIAM, types.JoinMethodAzure, types.JoinMethodTPM, types.JoinMethodOracle: - // These join methods must use gRPC register methods + // Some join methods require use of a specific RPC - reject those here. + // This would generally be a developer error - but can be triggered if + // the user has configured the wrong join method on the client-side. return nil, trace.AccessDenied("this token is only valid for the %s "+ "join method but the node has connected to the wrong endpoint, make "+ "sure your node is configured to use the %s join method", method, method) case types.JoinMethodGitHub: - claims, err := a.checkGitHubJoinRequest(ctx, req) + claims, err := a.checkGitHubJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Github = claims.JoinAttrs() @@ -250,7 +251,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodGitLab: - claims, err := a.checkGitLabJoinRequest(ctx, req) + claims, err := a.checkGitLabJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Gitlab = claims.JoinAttrs() @@ -259,7 +260,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodCircleCI: - claims, err := a.checkCircleCIJoinRequest(ctx, req) + claims, err := a.checkCircleCIJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Circleci = claims.JoinAttrs() @@ -268,7 +269,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodKubernetes: - claims, err := a.checkKubernetesJoinRequest(ctx, req) + claims, err := a.checkKubernetesJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Kubernetes = claims.JoinAttrs() @@ -277,7 +278,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodGCP: - claims, err := a.checkGCPJoinRequest(ctx, req) + claims, err := a.checkGCPJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Gcp = claims.JoinAttrs() @@ -286,7 +287,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodSpacelift: - claims, err := a.checkSpaceliftJoinRequest(ctx, req) + claims, err := a.checkSpaceliftJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Spacelift = claims.JoinAttrs() @@ -295,7 +296,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodTerraformCloud: - claims, err := a.checkTerraformCloudJoinRequest(ctx, req) + claims, err := a.checkTerraformCloudJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.TerraformCloud = claims.JoinAttrs() @@ -304,7 +305,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodBitbucket: - claims, err := a.checkBitbucketJoinRequest(ctx, req) + claims, err := a.checkBitbucketJoinRequest(ctx, req, provisionToken) if claims != nil { rawClaims = claims attrs.Bitbucket = claims.JoinAttrs() @@ -313,7 +314,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.Wrap(err) } case types.JoinMethodToken: - // carry on to common token checking logic + // no additional validation to perform - the name is enough. default: // this is a logic error, all valid join methods should be captured // above (empty join method will be set to JoinMethodToken by @@ -321,12 +322,6 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin return nil, trace.BadParameter("unrecognized token join method") } - // perform common token checks - provisionToken, err = a.checkTokenJoinRequestCommon(ctx, req) - if err != nil { - return nil, trace.Wrap(err) - } - // With all elements of the token validated, we can now generate & return // certificates. if req.Role == types.RoleBot { diff --git a/lib/auth/join_bitbucket.go b/lib/auth/join_bitbucket.go index b4264deef3f77..6560a5d8377de 100644 --- a/lib/auth/join_bitbucket.go +++ b/lib/auth/join_bitbucket.go @@ -33,15 +33,14 @@ type bitbucketIDTokenValidator interface { ) (*bitbucket.IDTokenClaims, error) } -func (a *Server) checkBitbucketJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*bitbucket.IDTokenClaims, error) { +func (a *Server) checkBitbucketJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*bitbucket.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("id_token not provided for bitbucket join request") } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } - token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("bitbucket join method only supports ProvisionTokenV2, '%T' was provided", pt) diff --git a/lib/auth/join_circleci.go b/lib/auth/join_circleci.go index ad7ec1d0796b8..c83aabf961df8 100644 --- a/lib/auth/join_circleci.go +++ b/lib/auth/join_circleci.go @@ -28,14 +28,14 @@ import ( "github.com/gravitational/teleport/lib/circleci" ) -func (a *Server) checkCircleCIJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*circleci.IDTokenClaims, error) { +func (a *Server) checkCircleCIJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*circleci.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("IDToken not provided for %q join request", types.JoinMethodCircleCI) } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("%q join method only support ProvisionTokenV2, '%T' was provided", types.JoinMethodCircleCI, pt) diff --git a/lib/auth/join_gcp.go b/lib/auth/join_gcp.go index a6cf1db4ccd7e..29b6001296541 100644 --- a/lib/auth/join_gcp.go +++ b/lib/auth/join_gcp.go @@ -34,15 +34,14 @@ type gcpIDTokenValidator interface { Validate(ctx context.Context, token string) (*gcp.IDTokenClaims, error) } -func (a *Server) checkGCPJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*gcp.IDTokenClaims, error) { +func (a *Server) checkGCPJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*gcp.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("IDToken not provided for GCP join request") } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } - token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("gcp join method only supports ProvisionTokenV2, '%T' was provided", pt) diff --git a/lib/auth/join_github.go b/lib/auth/join_github.go index 226f1c501c6b0..5935fd3b64bea 100644 --- a/lib/auth/join_github.go +++ b/lib/auth/join_github.go @@ -41,15 +41,14 @@ type ghaIDTokenJWKSValidator func( now time.Time, jwksData []byte, token string, ) (*githubactions.IDTokenClaims, error) -func (a *Server) checkGitHubJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*githubactions.IDTokenClaims, error) { +func (a *Server) checkGitHubJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*githubactions.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("IDToken not provided for Github join request") } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } - token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("github join method only supports ProvisionTokenV2, '%T' was provided", pt) @@ -69,6 +68,7 @@ func (a *Server) checkGitHubJoinRequest(ctx context.Context, req *types.Register } var claims *githubactions.IDTokenClaims + var err error if token.Spec.GitHub.StaticJWKS != "" { claims, err = a.ghaIDTokenJWKSValidator( a.clock.Now().UTC(), diff --git a/lib/auth/join_gitlab.go b/lib/auth/join_gitlab.go index c222d8a1fd964..3e60112b613f4 100644 --- a/lib/auth/join_gitlab.go +++ b/lib/auth/join_gitlab.go @@ -39,21 +39,21 @@ type gitlabIDTokenValidator interface { ) (*gitlab.IDTokenClaims, error) } -func (a *Server) checkGitLabJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*gitlab.IDTokenClaims, error) { +func (a *Server) checkGitLabJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*gitlab.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("IDToken not provided for gitlab join request") } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } - token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("gitlab join method only supports ProvisionTokenV2, '%T' was provided", pt) } var claims *gitlab.IDTokenClaims + var err error if token.Spec.GitLab.StaticJWKS != "" { claims, err = a.gitlabIDTokenValidator.ValidateTokenWithJWKS( ctx, []byte(token.Spec.GitLab.StaticJWKS), req.IDToken, diff --git a/lib/auth/join_kubernetes.go b/lib/auth/join_kubernetes.go index d5bbc6586d831..0e2ad83280016 100644 --- a/lib/auth/join_kubernetes.go +++ b/lib/auth/join_kubernetes.go @@ -36,14 +36,14 @@ type k8sTokenReviewValidator interface { type k8sJWKSValidator func(now time.Time, jwksData []byte, clusterName string, token string) (*kubetoken.ValidationResult, error) -func (a *Server) checkKubernetesJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*kubetoken.ValidationResult, error) { +func (a *Server) checkKubernetesJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + unversionedToken types.ProvisionToken, +) (*kubetoken.ValidationResult, error) { if req.IDToken == "" { return nil, trace.BadParameter("IDToken not provided for Kubernetes join request") } - unversionedToken, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } token, ok := unversionedToken.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter( diff --git a/lib/auth/join_spacelift.go b/lib/auth/join_spacelift.go index 30fecf5424abd..a0c609e3653ed 100644 --- a/lib/auth/join_spacelift.go +++ b/lib/auth/join_spacelift.go @@ -36,15 +36,14 @@ type spaceliftIDTokenValidator interface { ) (*spacelift.IDTokenClaims, error) } -func (a *Server) checkSpaceliftJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*spacelift.IDTokenClaims, error) { +func (a *Server) checkSpaceliftJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*spacelift.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("id_token not provided for spacelift join request") } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } - token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("spacelift join method only supports ProvisionTokenV2, '%T' was provided", pt) diff --git a/lib/auth/join_terraformcloud.go b/lib/auth/join_terraformcloud.go index 9302004858608..7123a83f74dc2 100644 --- a/lib/auth/join_terraformcloud.go +++ b/lib/auth/join_terraformcloud.go @@ -36,15 +36,14 @@ type terraformCloudIDTokenValidator interface { ) (*terraformcloud.IDTokenClaims, error) } -func (a *Server) checkTerraformCloudJoinRequest(ctx context.Context, req *types.RegisterUsingTokenRequest) (*terraformcloud.IDTokenClaims, error) { +func (a *Server) checkTerraformCloudJoinRequest( + ctx context.Context, + req *types.RegisterUsingTokenRequest, + pt types.ProvisionToken, +) (*terraformcloud.IDTokenClaims, error) { if req.IDToken == "" { return nil, trace.BadParameter("id_token not provided for terraform_cloud join request") } - pt, err := a.GetToken(ctx, req.Token) - if err != nil { - return nil, trace.Wrap(err) - } - token, ok := pt.(*types.ProvisionTokenV2) if !ok { return nil, trace.BadParameter("terraform_cloud join method only supports ProvisionTokenV2, '%T' was provided", pt) diff --git a/web/packages/teleport/src/services/audit/makeEvent.ts b/web/packages/teleport/src/services/audit/makeEvent.ts index 6953b6cfead49..014909f9e71fb 100644 --- a/web/packages/teleport/src/services/audit/makeEvent.ts +++ b/web/packages/teleport/src/services/audit/makeEvent.ts @@ -1419,15 +1419,15 @@ export const formatters: Formatters = { [eventCodes.BOT_JOIN]: { type: 'bot.join', desc: 'Bot Joined', - format: ({ bot_name, method }) => { - return `Bot [${bot_name}] joined the cluster using the [${method}] join method`; + format: ({ bot_name, method, token_name }) => { + return `Bot [${bot_name}] joined the cluster using the [${method}] join method and the [${token_name || 'unknown'}] token`; }, }, [eventCodes.BOT_JOIN_FAILURE]: { type: 'bot.join', desc: 'Bot Join Failed', - format: ({ bot_name }) => { - return `Bot [${bot_name || 'unknown'}] failed to join the cluster`; + format: ({ bot_name, method, token_name }) => { + return `Bot [${bot_name || 'unknown'}] failed to join the cluster using the [${method || 'unknown'}] join method and the [${token_name || 'unknown'}] token`; }, }, [eventCodes.INSTANCE_JOIN]: { diff --git a/web/packages/teleport/src/services/audit/types.ts b/web/packages/teleport/src/services/audit/types.ts index 628b1f7de97d4..8fb71c1e65d35 100644 --- a/web/packages/teleport/src/services/audit/types.ts +++ b/web/packages/teleport/src/services/audit/types.ts @@ -1316,6 +1316,7 @@ export type RawEvents = { { bot_name: string; method: string; + token_name: string; } >; [eventCodes.BOT_JOIN_FAILURE]: RawEvent< @@ -1323,6 +1324,7 @@ export type RawEvents = { { bot_name: string; method: string; + token_name: string; } >; [eventCodes.INSTANCE_JOIN]: RawEvent<