Add soft delete for revoked devices

Admins could only revoke devices, so never-enrolled and revoked
inventory rows piled up with no way to remove them. Soft-delete
is limited to REVOKED devices (revoke first), and ITAM GC now
hard-deletes PENDING/REVOKED orphans with no API key, postures,
or valid enrollment token—including user tombstones without
history.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-07-30 10:11:08 +02:00
parent 41da4bbad1
commit 7731566c68
23 changed files with 773 additions and 61 deletions

View File

@@ -58,6 +58,10 @@ var (
// cannot be exchanged for the device.
ErrEnrollmentTokenInvalid = errors.New("enrollment token invalid")
// ErrDeviceNotDeletable is returned when a device cannot be soft-deleted
// because it is not REVOKED.
ErrDeviceNotDeletable = errors.New("device cannot be deleted")
// ErrCorrelationIDRequired is returned when a posture result is
// missing a correlation ID.
ErrCorrelationIDRequired = errors.New("correlation_id is required")
@@ -553,6 +557,43 @@ func (s *Service) RevokeDevice(
return device, nil
}
func (s *Service) DeleteDevice(
ctx context.Context,
scope coredata.Scoper,
deviceID gid.GID,
) (*coredata.Device, error) {
device := &coredata.Device{}
err := s.pg.WithTx(
ctx,
func(ctx context.Context, conn pg.Tx) error {
if err := device.LoadByIDForUpdate(ctx, conn, scope, deviceID); err != nil {
return fmt.Errorf("cannot load device: %w", err)
}
if device.State != coredata.DeviceStateRevoked {
return ErrDeviceNotDeletable
}
if err := device.SoftDelete(ctx, conn, scope); err != nil {
return fmt.Errorf("cannot soft delete device: %w", err)
}
var token coredata.DeviceEnrollmentToken
if err := token.DeleteByDeviceID(ctx, conn, scope, device.ID); err != nil {
return fmt.Errorf("cannot delete device enrollment tokens: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return device, nil
}
func (s *Service) SetDeviceOwner(
ctx context.Context,
scope coredata.Scoper,