diff --git a/src/Api/AdminConsole/Models/Response/EventResponseModel.cs b/src/Api/AdminConsole/Models/Response/EventResponseModel.cs index 68695b3ab8..779dc44823 100644 --- a/src/Api/AdminConsole/Models/Response/EventResponseModel.cs +++ b/src/Api/AdminConsole/Models/Response/EventResponseModel.cs @@ -1,4 +1,6 @@ -using Bit.Core.Enums; +#nullable enable + +using Bit.Core.Enums; using Bit.Core.Models.Api; using Bit.Core.Models.Data; @@ -34,6 +36,7 @@ public class EventResponseModel : ResponseModel DomainName = ev.DomainName; SecretId = ev.SecretId; ServiceAccountId = ev.ServiceAccountId; + SecretIds = ev.SecretIds; } public EventType Type { get; set; } @@ -56,4 +59,5 @@ public class EventResponseModel : ResponseModel public string DomainName { get; set; } public Guid? SecretId { get; set; } public Guid? ServiceAccountId { get; set; } + public string? SecretIds { get; set; } } diff --git a/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs b/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs index 0609a4d782..6c10eff58f 100644 --- a/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs +++ b/src/Api/AdminConsole/Public/Models/Response/EventResponseModel.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +#nullable enable + +using System.ComponentModel.DataAnnotations; using Bit.Core.Enums; using Bit.Core.Models.Data; @@ -29,6 +31,7 @@ public class EventResponseModel : IResponseModel InstallationId = ev.InstallationId; SecretId = ev.SecretId; ServiceAccountId = ev.ServiceAccountId; + SecretIds = ev.SecretIds; } /// @@ -101,4 +104,6 @@ public class EventResponseModel : IResponseModel /// /// e68b8629-85eb-4929-92c0-b84464976ba4 public Guid? ServiceAccountId { get; set; } + + public string? SecretIds { get; set; } } diff --git a/src/Api/SecretsManager/Controllers/SecretsController.cs b/src/Api/SecretsManager/Controllers/SecretsController.cs index 519bc328fa..2abcc78ebc 100644 --- a/src/Api/SecretsManager/Controllers/SecretsController.cs +++ b/src/Api/SecretsManager/Controllers/SecretsController.cs @@ -231,7 +231,7 @@ public class SecretsController : Controller await _deleteSecretCommand.DeleteSecrets(secretsToDelete); var responses = results.Select(r => new BulkDeleteResponseModel(r.Secret.Id, r.Error)); - await LogSecretsEventAsync(secretsToDelete, EventType.Secret_Deleted); + await LogSecretsEventAsync(secretsToDelete, EventType.Secrets_Deleted_Bulk); return new ListResponseModel(responses); } @@ -251,7 +251,7 @@ public class SecretsController : Controller throw new NotFoundException(); } - await LogSecretsEventAsync(secrets, EventType.Secret_Retrieved); + await LogSecretsEventAsync(secrets, EventType.Secrets_Retrieved_Bulk); var responses = secrets.Select(s => new BaseSecretResponseModel(s)); return new ListResponseModel(responses); diff --git a/src/Core/AdminConsole/Entities/Event.cs b/src/Core/AdminConsole/Entities/Event.cs index 2a6b6664c2..6d6e9a9d30 100644 --- a/src/Core/AdminConsole/Entities/Event.cs +++ b/src/Core/AdminConsole/Entities/Event.cs @@ -33,6 +33,7 @@ public class Event : ITableObject, IEvent DomainName = e.DomainName; SecretId = e.SecretId; ServiceAccountId = e.ServiceAccountId; + SecretIds = e.SecretIds; } public Guid Id { get; set; } @@ -57,6 +58,7 @@ public class Event : ITableObject, IEvent public string? DomainName { get; set; } public Guid? SecretId { get; set; } public Guid? ServiceAccountId { get; set; } + public string? SecretIds { get; set; } public void SetNewId() { diff --git a/src/Core/AdminConsole/Enums/EventType.cs b/src/Core/AdminConsole/Enums/EventType.cs index 2359b922d8..7dc06fa666 100644 --- a/src/Core/AdminConsole/Enums/EventType.cs +++ b/src/Core/AdminConsole/Enums/EventType.cs @@ -93,4 +93,6 @@ public enum EventType : int Secret_Created = 2101, Secret_Edited = 2102, Secret_Deleted = 2103, + Secrets_Retrieved_Bulk = 2104, + Secrets_Deleted_Bulk = 2105, } diff --git a/src/Core/AdminConsole/Models/Data/EventMessage.cs b/src/Core/AdminConsole/Models/Data/EventMessage.cs index 6d2a1f2b4e..4adede2ff2 100644 --- a/src/Core/AdminConsole/Models/Data/EventMessage.cs +++ b/src/Core/AdminConsole/Models/Data/EventMessage.cs @@ -1,4 +1,7 @@ -using Bit.Core.Context; +#nullable enable + +using System.ComponentModel.DataAnnotations; +using Bit.Core.Context; using Bit.Core.Enums; namespace Bit.Core.Models.Data; @@ -14,8 +17,10 @@ public class EventMessage : IEvent DeviceType = currentContext.DeviceType; } - public DateTime Date { get; set; } - public EventType Type { get; set; } + [Required] + public DateTime Date { get; set; } = DateTime.Now; + [Required] + public EventType Type { get; set; } = EventType.Cipher_Created; public Guid? UserId { get; set; } public Guid? OrganizationId { get; set; } public Guid? InstallationId { get; set; } @@ -29,10 +34,14 @@ public class EventMessage : IEvent public Guid? ProviderOrganizationId { get; set; } public Guid? ActingUserId { get; set; } public DeviceType? DeviceType { get; set; } - public string IpAddress { get; set; } + [Required] + public string IpAddress { get; set; } = string.Empty; + public Guid? IdempotencyId { get; private set; } = Guid.NewGuid(); public EventSystemUser? SystemUser { get; set; } - public string DomainName { get; set; } + [Required] + public string DomainName { get; set; } = string.Empty; public Guid? SecretId { get; set; } + public string? SecretIds { get; set; } public Guid? ServiceAccountId { get; set; } } diff --git a/src/Core/AdminConsole/Models/Data/EventTableEntity.cs b/src/Core/AdminConsole/Models/Data/EventTableEntity.cs index 7e863b128c..5afd7da737 100644 --- a/src/Core/AdminConsole/Models/Data/EventTableEntity.cs +++ b/src/Core/AdminConsole/Models/Data/EventTableEntity.cs @@ -33,6 +33,7 @@ public class AzureEvent : ITableEntity public string DomainName { get; set; } public Guid? SecretId { get; set; } public Guid? ServiceAccountId { get; set; } + public string SecretIds { get; set; } public EventTableEntity ToEventTableEntity() { @@ -62,7 +63,8 @@ public class AzureEvent : ITableEntity SystemUser = SystemUser.HasValue ? (EventSystemUser)SystemUser.Value : null, DomainName = DomainName, SecretId = SecretId, - ServiceAccountId = ServiceAccountId + ServiceAccountId = ServiceAccountId, + SecretIds = SecretIds, }; } } @@ -93,6 +95,7 @@ public class EventTableEntity : IEvent DomainName = e.DomainName; SecretId = e.SecretId; ServiceAccountId = e.ServiceAccountId; + SecretIds = e.SecretIds; } public string PartitionKey { get; set; } @@ -120,6 +123,7 @@ public class EventTableEntity : IEvent public string DomainName { get; set; } public Guid? SecretId { get; set; } public Guid? ServiceAccountId { get; set; } + public string SecretIds { get; set; } public AzureEvent ToAzureEvent() { @@ -149,7 +153,8 @@ public class EventTableEntity : IEvent SystemUser = SystemUser.HasValue ? (int)SystemUser.Value : null, DomainName = DomainName, SecretId = SecretId, - ServiceAccountId = ServiceAccountId + ServiceAccountId = ServiceAccountId, + SecretIds = SecretIds }; } @@ -215,6 +220,15 @@ public class EventTableEntity : IEvent }); } + if (e.SecretIds != null) + { + entities.Add(new EventTableEntity(e) + { + PartitionKey = pKey, + RowKey = $"SecretIds={e.SecretIds}__Date={dateKey}__Uniquifier={uniquifier}" + }); + } + return entities; } diff --git a/src/Core/AdminConsole/Models/Data/IEvent.cs b/src/Core/AdminConsole/Models/Data/IEvent.cs index 6a177e39ca..361e1e4060 100644 --- a/src/Core/AdminConsole/Models/Data/IEvent.cs +++ b/src/Core/AdminConsole/Models/Data/IEvent.cs @@ -24,4 +24,5 @@ public interface IEvent string DomainName { get; set; } Guid? SecretId { get; set; } Guid? ServiceAccountId { get; set; } + string SecretIds { get; set; } } diff --git a/src/Core/AdminConsole/Services/Implementations/EventService.cs b/src/Core/AdminConsole/Services/Implementations/EventService.cs index d21e6f25e8..e46246592f 100644 --- a/src/Core/AdminConsole/Services/Implementations/EventService.cs +++ b/src/Core/AdminConsole/Services/Implementations/EventService.cs @@ -414,48 +414,111 @@ public class EventService : IEventService var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); var eventMessages = new List(); - foreach (var secret in secrets) + if (IsBulkEventType(type)) { - if (!CanUseEvents(orgAbilities, secret.OrganizationId)) + var secretsByOrg = secrets.GroupBy(s => s.OrganizationId); + + foreach (var group in secretsByOrg) { - continue; + var orgId = group.Key; + + if (!CanUseEvents(orgAbilities, orgId)) + { + continue; + } + + IEnumerable secretIds = group.Select(s => s.Id); + + var e = new EventMessage(_currentContext) + { + OrganizationId = orgId, + Type = type, + SecretIds = string.Join(",", secretIds.Select(id => id.ToString())), + UserId = userId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + eventMessages.Add(e); } - - var e = new EventMessage(_currentContext) + } + else + { + foreach (var secret in secrets) { - OrganizationId = secret.OrganizationId, - Type = type, - SecretId = secret.Id, - UserId = userId, - Date = date.GetValueOrDefault(DateTime.UtcNow) - }; - eventMessages.Add(e); + if (!CanUseEvents(orgAbilities, secret.OrganizationId)) + { + continue; + } + + var e = new EventMessage(_currentContext) + { + OrganizationId = secret.OrganizationId, + Type = type, + SecretId = secret.Id, + UserId = userId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + eventMessages.Add(e); + } } await _eventWriteService.CreateManyAsync(eventMessages); } + public bool IsBulkEventType(EventType type) + { + return type == EventType.Secrets_Retrieved_Bulk || type == EventType.Secrets_Deleted_Bulk; + } + public async Task LogServiceAccountSecretsEventAsync(Guid serviceAccountId, IEnumerable secrets, EventType type, DateTime? date = null) { var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); var eventMessages = new List(); - foreach (var secret in secrets) + if (IsBulkEventType(type)) { - if (!CanUseEvents(orgAbilities, secret.OrganizationId)) + var secretsByOrg = secrets.GroupBy(s => s.OrganizationId); + + foreach (var group in secretsByOrg) { - continue; + var orgId = group.Key; + + if (!CanUseEvents(orgAbilities, orgId)) + { + continue; + } + + IEnumerable secretIds = group.Select(s => s.Id); + + var e = new EventMessage(_currentContext) + { + OrganizationId = orgId, + Type = type, + SecretIds = string.Join(",", secretIds.Select(id => id.ToString())), + UserId = serviceAccountId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + eventMessages.Add(e); } - - var e = new EventMessage(_currentContext) + } + else + { + foreach (var secret in secrets) { - OrganizationId = secret.OrganizationId, - Type = type, - SecretId = secret.Id, - ServiceAccountId = serviceAccountId, - Date = date.GetValueOrDefault(DateTime.UtcNow) - }; - eventMessages.Add(e); + if (!CanUseEvents(orgAbilities, secret.OrganizationId)) + { + continue; + } + + var e = new EventMessage(_currentContext) + { + OrganizationId = secret.OrganizationId, + Type = type, + SecretId = secret.Id, + ServiceAccountId = serviceAccountId, + Date = date.GetValueOrDefault(DateTime.UtcNow) + }; + eventMessages.Add(e); + } } await _eventWriteService.CreateManyAsync(eventMessages);