Add audit events for approving & denying an EnrollPairing (#69505)

* Add audit events for approving & denying an `EnrollPairing`

* Add web changes

* Drop `device.enroll_pairing.approve` from `eventGroupTypes`

* "not approved" -> "denied"

* Update wording around `device.enroll_pairing.approve` emitted on failure

While working on the RPC handler implementation, I realized that
`TV011W` is not emitted on MFA failures. This commit updates the godoc
and the fixture accordingly.

* Update `deny` audit event wording around expiry

In a typical scenario there will be no one to observe expiry, so this
event won't be emitted on enroll pairing expiry. I'll update the RFD
accordingly soon. For now just putting this here since I have no other
outstanding OSS PRs.

* Remove no longer necessary TODO comment

"Retryability" will be implemented one level above the storage layer, in
the RPC handler, so the comment here is no longer necessary.

* Remove `error` field from `device.enroll_pairing.deny`

Now that it's only going to be used for an explicit user denial, there's
no need to carry the `error` field.
This commit is contained in:
Maja Cieślak
2026-08-12 15:11:58 +00:00
committed by GitHub
parent 9fe5463469
commit efe6598170
10 changed files with 188 additions and 3 deletions
+77
View File
@@ -3534,6 +3534,83 @@ Example:
}
```
## device.enroll_pairing.approve
There are multiple events with the `device.enroll_pairing.approve` type.
### TV011I
Device Enroll Pairing Approved
Example:
```json
{
"cluster_name": "im-a-cluster-name",
"code": "TV011I",
"device": {
"asset_tag": "M2CQVQV64R",
"device_id": "",
"os_type": 4
},
"ei": 0,
"event": "device.enroll_pairing.approve",
"success": true,
"time": "2024-04-08T19:39:48.1Z",
"uid": "b8c4f0a2-5e6d-7f8a-0123-abc456789012",
"user": "llama"
}
```
### TV011W
Device Enroll Pairing Approval Failed
Example:
```json
{
"cluster_name": "im-a-cluster-name",
"code": "TV011W",
"device": {
"asset_tag": "M2CQVQV64R",
"device_id": "",
"os_type": 4
},
"ei": 0,
"error": "enroll pairing is not awaiting approval",
"event": "device.enroll_pairing.approve",
"success": false,
"time": "2024-04-08T19:40:48.1Z",
"uid": "b8c4f0a2-5e6d-7f8a-0123-abc456789013",
"user": "llama"
}
```
## device.enroll_pairing.deny
Device Enroll Pairing Denied
Example:
```json
{
"cluster_name": "im-a-cluster-name",
"code": "TV012W",
"device": {
"asset_tag": "M2CQVQV64R",
"device_id": "",
"os_type": 4
},
"ei": 0,
"event": "device.enroll_pairing.deny",
"success": false,
"time": "2024-04-08T19:41:48.1Z",
"uid": "c9d5a1b3-6f7e-8a9b-2345-bcd567890123",
"user": "llama"
}
```
## device.enroll_pairing.request
There are multiple events with the `device.enroll_pairing.request` type.
+8
View File
@@ -712,6 +712,14 @@ const (
// awaiting approval. It is also emitted on failure (e.g. an invalid token),
// before any transition, in which case there is no associated user.
DeviceEnrollPairingRequestEvent = "device.enroll_pairing.request"
// DeviceEnrollPairingApproveEvent is emitted when the user approves an enroll
// pairing in the Web UI, which lets the device retrieve its enrollment token.
// It is also emitted on a failed approval attempt, for example one arriving
// after the pairing expired or was already approved.
DeviceEnrollPairingApproveEvent = "device.enroll_pairing.approve"
// DeviceEnrollPairingDenyEvent is emitted when the user denies an enroll
// pairing in the Web UI.
DeviceEnrollPairingDenyEvent = "device.enroll_pairing.deny"
// BotJoinEvent is emitted when a bot joins
BotJoinEvent = "bot.join"
+7
View File
@@ -530,6 +530,13 @@ const (
// DeviceEnrollPairingRequestFailureCode is the device enroll pairing request
// failure code.
DeviceEnrollPairingRequestFailureCode = "TV010W"
// DeviceEnrollPairingApproveCode is the device enroll pairing approve code.
DeviceEnrollPairingApproveCode = "TV011I"
// DeviceEnrollPairingApproveFailureCode is the device enroll pairing approve
// failure code.
DeviceEnrollPairingApproveFailureCode = "TV011W"
// DeviceEnrollPairingDenyCode is the device enroll pairing deny code.
DeviceEnrollPairingDenyCode = "TV012W"
// LoginRuleCreateCode is the login rule create code.
LoginRuleCreateCode = "TLR00I"
+3 -1
View File
@@ -297,7 +297,9 @@ func FromEventFields(fields EventFields) (events.AuditEvent, error) {
DeviceEnrollTokenCreateEvent,
DeviceWebTokenCreateEvent,
DeviceAuthenticateConfirmEvent,
DeviceEnrollPairingRequestEvent:
DeviceEnrollPairingRequestEvent,
DeviceEnrollPairingApproveEvent,
DeviceEnrollPairingDenyEvent:
e = &events.DeviceEvent2{}
case LockCreatedEvent:
e = &events.LockCreate{}
+2
View File
@@ -156,6 +156,8 @@ var eventsMap = map[string]apievents.AuditEvent{
DeviceWebTokenCreateEvent: &apievents.DeviceEvent2{},
DeviceAuthenticateConfirmEvent: &apievents.DeviceEvent2{},
DeviceEnrollPairingRequestEvent: &apievents.DeviceEvent2{},
DeviceEnrollPairingApproveEvent: &apievents.DeviceEvent2{},
DeviceEnrollPairingDenyEvent: &apievents.DeviceEvent2{},
LockCreatedEvent: &apievents.LockCreate{},
LockDeletedEvent: &apievents.LockDelete{},
RecoveryCodeGeneratedEvent: &apievents.RecoveryCodeGenerate{},
-2
View File
@@ -208,8 +208,6 @@ func (s *EnrollPairingService) RequestEnrollPairingApproval(ctx context.Context,
}
const errNotAwaitingDevice = "enroll pairing is not awaiting a device"
// TODO(ravicious): Make the call retryable from the same device, as described
// in the Interrputibility section of RFD 32e.
if pairing.GetStatus().GetState() != devicepb.EnrollPairingState_ENROLL_PAIRING_STATE_AWAITING_DEVICE {
return nil, trace.CompareFailed(errNotAwaitingDevice)
}
@@ -208,6 +208,9 @@ export const EventIconMap: Record<EventCode, any> = {
[eventCodes.DEVICE_AUTHENTICATE_CONFIRM]: Icons.Info,
[eventCodes.DEVICE_ENROLL_PAIRING_REQUEST]: Icons.Info,
[eventCodes.DEVICE_ENROLL_PAIRING_REQUEST_FAILURE]: Icons.Info,
[eventCodes.DEVICE_ENROLL_PAIRING_APPROVE]: Icons.Info,
[eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE]: Icons.Info,
[eventCodes.DEVICE_ENROLL_PAIRING_DENY]: Icons.Info,
[eventCodes.MFA_DEVICE_ADD]: Icons.Info,
[eventCodes.MFA_DEVICE_DELETE]: Icons.Info,
[eventCodes.BILLING_CARD_CREATE]: Icons.CreditCard,
@@ -3321,6 +3321,52 @@ export const events = [
time: '2024-04-08T19:38:48.1Z',
uid: 'a7b3e9f1-4d5c-6a8b-9012-def345678902',
},
{
cluster_name: 'im-a-cluster-name',
code: 'TV011I',
device: {
asset_tag: 'M2CQVQV64R',
device_id: '',
os_type: 4,
},
ei: 0,
event: 'device.enroll_pairing.approve',
success: true,
time: '2024-04-08T19:39:48.1Z',
uid: 'b8c4f0a2-5e6d-7f8a-0123-abc456789012',
user: 'llama',
},
{
cluster_name: 'im-a-cluster-name',
code: 'TV011W',
device: {
asset_tag: 'M2CQVQV64R',
device_id: '',
os_type: 4,
},
ei: 0,
error: 'enroll pairing is not awaiting approval',
event: 'device.enroll_pairing.approve',
success: false,
time: '2024-04-08T19:40:48.1Z',
uid: 'b8c4f0a2-5e6d-7f8a-0123-abc456789013',
user: 'llama',
},
{
cluster_name: 'im-a-cluster-name',
code: 'TV012W',
device: {
asset_tag: 'M2CQVQV64R',
device_id: '',
os_type: 4,
},
ei: 0,
event: 'device.enroll_pairing.deny',
success: false,
time: '2024-04-08T19:41:48.1Z',
uid: 'c9d5a1b3-6f7e-8a9b-2345-bcd567890123',
user: 'llama',
},
{
cluster_name: 'im-a-cluster-name',
code: 'TLR00I',
@@ -1507,6 +1507,34 @@ export const formatters: Formatters = {
? `Device enrollment request failed${formatDevice(device)}: ${error}`
: `Device enrollment request failed${formatDevice(device)}`,
},
[eventCodes.DEVICE_ENROLL_PAIRING_APPROVE]: {
type: 'device.enroll_pairing.approve',
desc: 'Device Enroll Pairing Approved',
format: ({ user, device }) =>
`User [${user}] approved the device enrollment request${formatDevice(device)}`,
},
[eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE]: {
type: 'device.enroll_pairing.approve',
desc: 'Device Enroll Pairing Approval Failed',
format: ({ user, device, error }) => {
let msg = `User [${user}] failed to approve the device enrollment request${formatDevice(device)}`;
if (error) {
msg += `: ${error}`;
}
return msg;
},
},
[eventCodes.DEVICE_ENROLL_PAIRING_DENY]: {
type: 'device.enroll_pairing.deny',
desc: 'Device Enroll Pairing Denied',
format: ({ user, device, error }) => {
let msg = `Device enrollment request for user [${user}] was denied${formatDevice(device)}`;
if (error) {
msg += `: ${error}`;
}
return msg;
},
},
[eventCodes.X11_FORWARD]: {
type: 'x11-forward',
desc: 'X11 Forwarding Requested',
@@ -123,6 +123,9 @@ export const eventCodes = {
DEVICE_AUTHENTICATE_CONFIRM: 'TV009I',
DEVICE_ENROLL_PAIRING_REQUEST: 'TV010I',
DEVICE_ENROLL_PAIRING_REQUEST_FAILURE: 'TV010W',
DEVICE_ENROLL_PAIRING_APPROVE: 'TV011I',
DEVICE_ENROLL_PAIRING_APPROVE_FAILURE: 'TV011W',
DEVICE_ENROLL_PAIRING_DENY: 'TV012W',
EXEC_FAILURE: 'T3002E',
EXEC: 'T3002I',
GITHUB_CONNECTOR_CREATED: 'T8000I',
@@ -1434,6 +1437,17 @@ export type RawEvents = {
typeof eventCodes.DEVICE_ENROLL_PAIRING_REQUEST_FAILURE,
Merge<DeviceEventFields, { error?: string }>
>;
[eventCodes.DEVICE_ENROLL_PAIRING_APPROVE]: RawDeviceEvent<
typeof eventCodes.DEVICE_ENROLL_PAIRING_APPROVE
>;
[eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE]: RawEvent<
typeof eventCodes.DEVICE_ENROLL_PAIRING_APPROVE_FAILURE,
Merge<DeviceEventFields, { error?: string }>
>;
[eventCodes.DEVICE_ENROLL_PAIRING_DENY]: RawEvent<
typeof eventCodes.DEVICE_ENROLL_PAIRING_DENY,
Merge<DeviceEventFields, { error?: string }>
>;
[eventCodes.UNKNOWN]: RawEvent<
typeof eventCodes.UNKNOWN,
{