Skip to content

MWI: Add audit events for bound keypair joining #55701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from 5 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
97 changes: 97 additions & 0 deletions api/proto/teleport/legacy/types/events/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4823,6 +4823,9 @@ message OneOf {
events.MCPSessionEnd MCPSessionEnd = 217;
events.MCPSessionRequest MCPSessionRequest = 218;
events.MCPSessionNotification MCPSessionNotification = 219;
events.BoundKeypairRecovery BoundKeypairRecovery = 220;
events.BoundKeypairRotation BoundKeypairRotation = 221;
events.BoundKeypairJoinStateVerificationFailed BoundKeypairJoinStateVerificationFailed = 222;
}
}

Expand Down Expand Up @@ -8701,3 +8704,97 @@ message MCPSessionNotification {
(gogoproto.jsontag) = "message,omitempty"
];
}

// BoundKeypairRecovery is emitted when a client performs a self recovery using
// a bound_keypair joining token. This event is also emitted upon first join.
message BoundKeypairRecovery {
// Metadata is a common event metadata.
Metadata Metadata = 1 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Status contains common command or operation status fields.
Status Status = 2 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// ConnectionMetadata holds information about the connection
ConnectionMetadata Connection = 3 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// TokenName is the name of the provision token used to join.
string TokenName = 4 [(gogoproto.jsontag) = "token_name"];
// BotName is the name of the bot attempting to join, if any.
string BotName = 5 [(gogoproto.jsontag) = "bot_name,omitempty"];
// PublicKey is the public key at the completion of the joining process, in
// SSH authorized_keys format. If a keypair rotation occurred, this is the
// keypair trusted at the end of the join process.
string PublicKey = 6 [(gogoproto.jsontag) = "public_key,omitempty"];
// RecoveryCount is the recovery counter value at the time of this recovery.
uint32 RecoveryCount = 7 [(gogoproto.jsontag) = "recovery_count"];
// RecoveryMode is the bound keypair token's configured recovery mode.
string RecoveryMode = 8 [(gogoproto.jsontag) = "recovery_mode"];
}

// BoundKeypairRotation is emitted when a keypair rotation takes place.
message BoundKeypairRotation {
// Metadata is a common event metadata.
Metadata Metadata = 1 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Status contains common command or operation status fields.
Status Status = 2 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// ConnectionMetadata holds information about the connection
ConnectionMetadata Connection = 3 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// TokenName is the name of the provision token used to join.
string TokenName = 4 [(gogoproto.jsontag) = "token_name"];
// BotName is the name of the bot attempting to join, if any.
string BotName = 5 [(gogoproto.jsontag) = "bot_name,omitempty"];
// PreviousPublicKey is the previous public key in SSH authorized_keys format.
// On first join using a registration secret, this value will be empty.
string PreviousPublicKey = 6 [(gogoproto.jsontag) = "previous_public_key,omitempty"];
// NewPublicKey is the new public key after rotation. If rotation fails, this
// value will be empty.
string NewPublicKey = 7 [(gogoproto.jsontag) = "new_public_key,omitempty"];
}

// BoundKeypairJoinStateVerificationFailed is emitted when join state
// verification fails, potentially indicating a compromised keypair.
message BoundKeypairJoinStateVerificationFailed {
// Metadata is a common event metadata.
Metadata Metadata = 1 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// Status contains information about the failure.
Status Status = 2 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// ConnectionMetadata holds information about the connection
ConnectionMetadata Connection = 3 [
(gogoproto.nullable) = false,
(gogoproto.embed) = true,
(gogoproto.jsontag) = ""
];
// TokenName is the name of the provision token used to join.
string TokenName = 4 [(gogoproto.jsontag) = "token_name"];
// BotName is the name of the bot attempting to join, if any.
string BotName = 5 [(gogoproto.jsontag) = "bot_name,omitempty"];
}
66 changes: 66 additions & 0 deletions api/types/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -2598,3 +2598,69 @@ func (m *MCPSessionNotification) TrimToMaxSize(maxSize int) AuditEvent {
out.Message = m.Message.trimToMaxSize(maxSize)
return out
}

func (m *BoundKeypairRecovery) TrimToMaxSize(maxSize int) AuditEvent {
size := m.Size()
if size <= maxSize {
return m
}
out := utils.CloneProtoMsg(m)
out.Status = Status{}
out.TokenName = ""
out.BotName = ""
out.PublicKey = ""

maxSize = adjustedMaxSize(out, maxSize)
customFieldsCount := m.Status.nonEmptyStrs() + nonEmptyStrs(m.TokenName, m.BotName, m.PublicKey)
maxFieldsSize := maxSizePerField(maxSize, customFieldsCount)

out.Status = m.Status.trimToMaxSize(maxFieldsSize)
out.TokenName = trimStr(m.TokenName, maxFieldsSize)
out.BotName = trimStr(m.BotName, maxFieldsSize)
out.PublicKey = trimStr(m.PublicKey, maxFieldsSize)
return out
}

func (m *BoundKeypairRotation) TrimToMaxSize(maxSize int) AuditEvent {
size := m.Size()
if size <= maxSize {
return m
}
out := utils.CloneProtoMsg(m)
out.Status = Status{}
out.TokenName = ""
out.BotName = ""
out.PreviousPublicKey = ""
out.NewPublicKey = ""

maxSize = adjustedMaxSize(out, maxSize)
customFieldsCount := m.Status.nonEmptyStrs() + nonEmptyStrs(m.TokenName, m.BotName, m.PreviousPublicKey, m.NewPublicKey)
maxFieldsSize := maxSizePerField(maxSize, customFieldsCount)

out.Status = m.Status.trimToMaxSize(maxFieldsSize)
out.TokenName = trimStr(m.TokenName, maxFieldsSize)
out.BotName = trimStr(m.BotName, maxFieldsSize)
out.PreviousPublicKey = trimStr(m.PreviousPublicKey, maxFieldsSize)
out.NewPublicKey = trimStr(m.NewPublicKey, maxFieldsSize)
return out
}

func (m *BoundKeypairJoinStateVerificationFailed) TrimToMaxSize(maxSize int) AuditEvent {
size := m.Size()
if size <= maxSize {
return m
}
out := utils.CloneProtoMsg(m)
out.Status = Status{}
out.TokenName = ""
out.BotName = ""

maxSize = adjustedMaxSize(out, maxSize)
customFieldsCount := m.Status.nonEmptyStrs() + nonEmptyStrs(m.TokenName, m.BotName)
maxFieldsSize := maxSizePerField(maxSize, customFieldsCount)

out.Status = m.Status.trimToMaxSize(maxFieldsSize)
out.TokenName = trimStr(m.TokenName, maxFieldsSize)
out.BotName = trimStr(m.BotName, maxFieldsSize)
return out
}
Loading
Loading