Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 112 additions & 28 deletions lib/auth/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,87 @@ func setRemoteAddrFromContext(ctx context.Context, req *types.RegisterUsingToken
return nil
}

// handleJoinFailure logs and audits the failure of a join. It is intentionally
// designed to handle potential nullness of the input parameters.
func (a *Server) handleJoinFailure(
origErr error,
pt types.ProvisionToken,
attributeSource joinAttributeSourcer,
req *types.RegisterUsingTokenRequest,
) {
fields := logrus.Fields{}
if req != nil {
fields["role"] = req.Role
fields["host_id"] = req.HostID
fields["node_name"] = req.NodeName
}

// Fetch and encode attributes if they are available.
var attributesProto *apievents.Struct
if attributeSource != nil {
var err error
attributes, err := attributeSource.JoinAuditAttributes()
if err != nil {
log.WithError(err).Warn("Unable to fetch join attributes from join method")
}
fields["attributes"] = attributes
attributesProto, err = apievents.EncodeMap(attributes)
if err != nil {
log.WithError(err).Warn("Unable to encode join attributes for audit event")
}
}

// Add log fields from token if available.
if pt != nil {
fields["join_method"] = string(pt.GetJoinMethod())
fields["token_name"] = pt.GetSafeName()
}
log.WithError(origErr).WithFields(fields).Warn("Failure to join cluster occurred")

var evt apievents.AuditEvent
status := apievents.Status{
Success: false,
Error: origErr.Error(),
}
if req != nil && req.Role == types.RoleBot {
botJoinEvent := &apievents.BotJoin{
Metadata: apievents.Metadata{
Type: events.BotJoinEvent,
Code: events.BotJoinFailureCode,
},
Status: status,
Attributes: attributesProto,
}
if pt != nil {
botJoinEvent.Method = string(pt.GetJoinMethod())
botJoinEvent.TokenName = pt.GetSafeName()
}
evt = botJoinEvent
} else {
instanceJoinEvent := &apievents.InstanceJoin{
Metadata: apievents.Metadata{
Type: events.InstanceJoinEvent,
Code: events.InstanceJoinFailureCode,
},
Status: status,
Attributes: attributesProto,
}
if pt != nil {
instanceJoinEvent.Method = string(pt.GetJoinMethod())
instanceJoinEvent.TokenName = pt.GetSafeName()
}
if req != nil {
instanceJoinEvent.Role = string(req.Role)
instanceJoinEvent.NodeName = req.NodeName
instanceJoinEvent.HostID = req.HostID
}
evt = instanceJoinEvent
}
if err := a.emitter.EmitAuditEvent(a.closeCtx, evt); err != nil {
log.WithError(err).Warn("Failed to emit failed join event")
}
}

// RegisterUsingToken returns credentials for a new node to join the Teleport
// cluster using a previously issued token.
//
Expand All @@ -126,30 +207,21 @@ func setRemoteAddrFromContext(ctx context.Context, req *types.RegisterUsingToken
//
// If the token includes a specific join method, the rules for that join method
// will be checked.
func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsingTokenRequest) (_ *proto.Certs, err error) {
func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsingTokenRequest) (certs *proto.Certs, err error) {
var joinAttributeSrc joinAttributeSourcer
var provisionToken types.ProvisionToken
defer func() {
// Emit a log message and audit event on join failure.
if err != nil {
a.handleJoinFailure(err, provisionToken, joinAttributeSrc, req)
}
}()

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

method := a.tokenJoinMethod(ctx, req.Token)
defer func() {
if err == nil {
return
}
level := logrus.WarnLevel
if trace.IsAccessDenied(err) {
level = logrus.DebugLevel
}
log.WithFields(logrus.Fields{
"node_name": req.NodeName,
"host_id": req.HostID,
"role": req.Role,
"method": method,
logrus.ErrorKey: err,
}).Log(level, "Agent has failed to join the cluster.")
}()

var joinAttributeSrc joinAttributeSourcer
switch method {
case types.JoinMethodEC2:
if err := a.checkEC2JoinRequest(ctx, req); err != nil {
Expand All @@ -162,40 +234,52 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin
"sure your node is configured to use the %s join method", method, method)
case types.JoinMethodGitHub:
claims, err := a.checkGitHubJoinRequest(ctx, req)
Comment thread
ibeckermayer marked this conversation as resolved.
if claims != nil {
joinAttributeSrc = claims
}
if err != nil {
return nil, trace.Wrap(err)
}
joinAttributeSrc = claims
case types.JoinMethodGitLab:
claims, err := a.checkGitLabJoinRequest(ctx, req)
if claims != nil {
joinAttributeSrc = claims
}
if err != nil {
return nil, trace.Wrap(err)
}
joinAttributeSrc = claims
case types.JoinMethodCircleCI:
claims, err := a.checkCircleCIJoinRequest(ctx, req)
if claims != nil {
joinAttributeSrc = claims
}
if err != nil {
return nil, trace.Wrap(err)
}
joinAttributeSrc = claims
case types.JoinMethodKubernetes:
claims, err := a.checkKubernetesJoinRequest(ctx, req)
if claims != nil {
joinAttributeSrc = claims
}
if err != nil {
return nil, trace.Wrap(err)
}
joinAttributeSrc = claims
case types.JoinMethodGCP:
claims, err := a.checkGCPJoinRequest(ctx, req)
if claims != nil {
joinAttributeSrc = claims
}
if err != nil {
return nil, trace.Wrap(err)
}
joinAttributeSrc = claims
case types.JoinMethodSpacelift:
claims, err := a.checkSpaceliftJoinRequest(ctx, req)
if claims != nil {
joinAttributeSrc = claims
}
if err != nil {
return nil, trace.Wrap(err)
}
joinAttributeSrc = claims
case types.JoinMethodToken:
// carry on to common token checking logic
default:
Expand All @@ -206,18 +290,18 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin
}

// perform common token checks
provisionToken, err := a.checkTokenJoinRequestCommon(ctx, req)
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 {
certs, err := a.generateCertsBot(ctx, provisionToken, req, joinAttributeSrc)
certs, err = a.generateCertsBot(ctx, provisionToken, req, joinAttributeSrc)
return certs, trace.Wrap(err)
}
certs, err := a.generateCerts(ctx, provisionToken, req, joinAttributeSrc)
certs, err = a.generateCerts(ctx, provisionToken, req, joinAttributeSrc)
return certs, trace.Wrap(err)
}

Expand Down
21 changes: 18 additions & 3 deletions lib/auth/join_azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,22 @@ func generateAzureChallenge() (string, error) {
// The caller must provide a ChallengeResponseFunc which returns a
// *proto.RegisterUsingAzureMethodRequest with a signed attested data document
// including the challenge as a nonce.
func (a *Server) RegisterUsingAzureMethod(ctx context.Context, challengeResponse client.RegisterAzureChallengeResponseFunc, opts ...azureRegisterOption) (*proto.Certs, error) {
func (a *Server) RegisterUsingAzureMethod(
ctx context.Context,
challengeResponse client.RegisterAzureChallengeResponseFunc,
opts ...azureRegisterOption,
) (certs *proto.Certs, err error) {
var provisionToken types.ProvisionToken
var joinRequest *types.RegisterUsingTokenRequest
defer func() {
// Emit a log message and audit event on join failure.
if err != nil {
a.handleJoinFailure(
err, provisionToken, nil, joinRequest,
)
}
}()

cfg := &azureRegisterConfig{}
for _, opt := range opts {
opt(cfg)
Expand All @@ -362,7 +377,7 @@ func (a *Server) RegisterUsingAzureMethod(ctx context.Context, challengeResponse
return nil, trace.Wrap(err)
}

provisionToken, err := a.checkTokenJoinRequestCommon(ctx, req.RegisterUsingTokenRequest)
provisionToken, err = a.checkTokenJoinRequestCommon(ctx, req.RegisterUsingTokenRequest)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -380,7 +395,7 @@ func (a *Server) RegisterUsingAzureMethod(ctx context.Context, challengeResponse
)
return certs, trace.Wrap(err)
}
certs, err := a.generateCerts(
certs, err = a.generateCerts(
ctx,
provisionToken,
req.RegisterUsingTokenRequest,
Expand Down
41 changes: 18 additions & 23 deletions lib/auth/join_iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"github.com/aws/aws-sdk-go/service/sts"
"github.com/coreos/go-semver/semver"
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client"
Expand Down Expand Up @@ -340,7 +339,22 @@ func withFips(fips bool) iamRegisterOption {
// The caller must provide a ChallengeResponseFunc which returns a
// *types.RegisterUsingTokenRequest with a signed sts:GetCallerIdentity request
// including the challenge as a signed header.
func (a *Server) RegisterUsingIAMMethod(ctx context.Context, challengeResponse client.RegisterIAMChallengeResponseFunc, opts ...iamRegisterOption) (_ *proto.Certs, err error) {
func (a *Server) RegisterUsingIAMMethod(
ctx context.Context,
challengeResponse client.RegisterIAMChallengeResponseFunc,
opts ...iamRegisterOption,
) (certs *proto.Certs, err error) {
var provisionToken types.ProvisionToken
var joinRequest *types.RegisterUsingTokenRequest
defer func() {
// Emit a log message and audit event on join failure.
if err != nil {
a.handleJoinFailure(
err, provisionToken, nil, joinRequest,
)
}
}()

cfg := defaultIAMRegisterConfig(a.fips)
for _, opt := range opts {
opt(cfg)
Expand All @@ -360,30 +374,11 @@ func (a *Server) RegisterUsingIAMMethod(ctx context.Context, challengeResponse c
return nil, trace.Wrap(err)
}

var method types.JoinMethod = "unknown"
defer func() {
if err == nil {
return
}
level := logrus.WarnLevel
if trace.IsAccessDenied(err) {
level = logrus.DebugLevel
}
log.WithFields(logrus.Fields{
"node_name": req.RegisterUsingTokenRequest.NodeName,
"host_id": req.RegisterUsingTokenRequest.HostID,
"role": req.RegisterUsingTokenRequest.Role,
"method": method,
logrus.ErrorKey: err,
}).Log(level, "Agent has failed to join the cluster.")
}()

// perform common token checks
provisionToken, err := a.checkTokenJoinRequestCommon(ctx, req.RegisterUsingTokenRequest)
provisionToken, err = a.checkTokenJoinRequestCommon(ctx, req.RegisterUsingTokenRequest)
if err != nil {
return nil, trace.Wrap(err)
}
method = provisionToken.GetJoinMethod()

// check that the GetCallerIdentity request is valid and matches the token
if err := a.checkIAMRequest(ctx, challenge, req, cfg); err != nil {
Expand All @@ -394,7 +389,7 @@ func (a *Server) RegisterUsingIAMMethod(ctx context.Context, challengeResponse c
certs, err := a.generateCertsBot(ctx, provisionToken, req.RegisterUsingTokenRequest, nil)
return certs, trace.Wrap(err)
}
certs, err := a.generateCerts(ctx, provisionToken, req.RegisterUsingTokenRequest, nil)
certs, err = a.generateCerts(ctx, provisionToken, req.RegisterUsingTokenRequest, nil)
return certs, trace.Wrap(err)
}

Expand Down
4 changes: 4 additions & 0 deletions lib/events/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,12 @@ const (

// BotJoinCode is the 'bot.join' event code.
BotJoinCode = "TJ001I"
// BotJoinFailureCode is the 'bot.join' event code for failures.
BotJoinFailureCode = "TJ001E"
// InstanceJoinCode is the 'node.join' event code.
InstanceJoinCode = "TJ002I"
// InstanceJoinFailureCode is the 'node.join' event code for failures.
InstanceJoinFailureCode = "TJ002E"

// BotCreateCode is the `bot.create` event code.
BotCreateCode = "TB001I"
Expand Down
2 changes: 2 additions & 0 deletions web/packages/teleport/src/Audit/EventList/EventTypeCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,9 @@ const EventIconMap: Record<EventCode, any> = {
[eventCodes.SSMRUN_SUCCESS]: Icons.Info,
[eventCodes.SSMRUN_FAIL]: Icons.Info,
[eventCodes.BOT_JOIN]: Icons.Info,
[eventCodes.BOT_JOIN_FAILURE]: Icons.Warning,
[eventCodes.INSTANCE_JOIN]: Icons.Info,
[eventCodes.INSTANCE_JOIN_FAILURE]: Icons.Warning,
[eventCodes.LOGIN_RULE_CREATE]: Icons.Info,
[eventCodes.LOGIN_RULE_DELETE]: Icons.Info,
[eventCodes.SAML_IDP_AUTH_ATTEMPT]: Icons.Info,
Expand Down
Loading