Browse Source

[SM-389] Event log for service account (#2674)

pull/2741/head
Oscar Hinton 3 years ago committed by GitHub
parent
commit
64e0a981c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/Api/Models/Response/EventResponseModel.cs
  2. 25
      src/Api/SecretsManager/Controllers/SecretsController.cs
  3. 5
      src/Core/Entities/Event.cs
  4. 4
      src/Core/Enums/EventType.cs
  5. 2
      src/Core/Models/Data/EventMessage.cs
  6. 22
      src/Core/Models/Data/EventTableEntity.cs
  7. 2
      src/Core/Models/Data/IEvent.cs
  8. 2
      src/Core/Services/IEventService.cs
  9. 24
      src/Core/Services/Implementations/EventService.cs
  10. 6
      src/Core/Services/NoopImplementations/NoopEventService.cs
  11. 12
      src/Sql/dbo/Stored Procedures/Event_Create.sql
  12. 4
      src/Sql/dbo/Tables/Event.sql
  13. 100
      util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql
  14. 2138
      util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs
  15. 36
      util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.cs
  16. 6
      util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs
  17. 2149
      util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs
  18. 34
      util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.cs
  19. 6
      util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs
  20. 2136
      util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs
  21. 34
      util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.cs
  22. 6
      util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs

4
src/Api/Models/Response/EventResponseModel.cs

@ -32,6 +32,8 @@ public class EventResponseModel : ResponseModel @@ -32,6 +32,8 @@ public class EventResponseModel : ResponseModel
InstallationId = ev.InstallationId;
SystemUser = ev.SystemUser;
DomainName = ev.DomainName;
SecretId = ev.SecretId;
ServiceAccountId = ev.ServiceAccountId;
}
public EventType Type { get; set; }
@ -52,4 +54,6 @@ public class EventResponseModel : ResponseModel @@ -52,4 +54,6 @@ public class EventResponseModel : ResponseModel
public string IpAddress { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
}

25
src/Api/SecretsManager/Controllers/SecretsController.cs

@ -4,6 +4,7 @@ using Bit.Api.SecretsManager.Models.Response; @@ -4,6 +4,7 @@ using Bit.Api.SecretsManager.Models.Response;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Identity;
using Bit.Core.SecretsManager.Commands.Secrets.Interfaces;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.SecretsManager.Repositories;
@ -18,22 +19,32 @@ namespace Bit.Api.SecretsManager.Controllers; @@ -18,22 +19,32 @@ namespace Bit.Api.SecretsManager.Controllers;
public class SecretsController : Controller
{
private readonly ICurrentContext _currentContext;
private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository;
private readonly ISecretRepository _secretRepository;
private readonly ICreateSecretCommand _createSecretCommand;
private readonly IUpdateSecretCommand _updateSecretCommand;
private readonly IDeleteSecretCommand _deleteSecretCommand;
private readonly IUserService _userService;
public SecretsController(ISecretRepository secretRepository, IProjectRepository projectRepository, ICreateSecretCommand createSecretCommand, IUpdateSecretCommand updateSecretCommand, IDeleteSecretCommand deleteSecretCommand, IUserService userService, ICurrentContext currentContext)
private readonly IEventService _eventService;
public SecretsController(
ICurrentContext currentContext,
IProjectRepository projectRepository,
ISecretRepository secretRepository,
ICreateSecretCommand createSecretCommand,
IUpdateSecretCommand updateSecretCommand,
IDeleteSecretCommand deleteSecretCommand,
IUserService userService,
IEventService eventService)
{
_currentContext = currentContext;
_projectRepository = projectRepository;
_secretRepository = secretRepository;
_createSecretCommand = createSecretCommand;
_updateSecretCommand = updateSecretCommand;
_deleteSecretCommand = deleteSecretCommand;
_projectRepository = projectRepository;
_userService = userService;
_eventService = eventService;
}
[HttpGet("organizations/{organizationId}/secrets")]
@ -81,6 +92,12 @@ public class SecretsController : Controller @@ -81,6 +92,12 @@ public class SecretsController : Controller
throw new NotFoundException();
}
if (_currentContext.ClientType == ClientType.ServiceAccount)
{
var userId = _userService.GetProperUserId(User).Value;
await _eventService.LogServiceAccountSecretEventAsync(userId, secret, EventType.Secret_Retrieved);
}
return new SecretResponseModel(secret);
}

5
src/Core/Entities/Event.cs

@ -29,6 +29,8 @@ public class Event : ITableObject<Guid>, IEvent @@ -29,6 +29,8 @@ public class Event : ITableObject<Guid>, IEvent
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;
DomainName = e.DomainName;
SecretId = e.SecretId;
ServiceAccountId = e.ServiceAccountId;
}
public Guid Id { get; set; }
@ -51,7 +53,8 @@ public class Event : ITableObject<Guid>, IEvent @@ -51,7 +53,8 @@ public class Event : ITableObject<Guid>, IEvent
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
public void SetNewId()
{

4
src/Core/Enums/EventType.cs

@ -80,5 +80,7 @@ public enum EventType : int @@ -80,5 +80,7 @@ public enum EventType : int
OrganizationDomain_Added = 2000,
OrganizationDomain_Removed = 2001,
OrganizationDomain_Verified = 2002,
OrganizationDomain_NotVerified = 2003
OrganizationDomain_NotVerified = 2003,
Secret_Retrieved = 2100,
}

2
src/Core/Models/Data/EventMessage.cs

@ -33,4 +33,6 @@ public class EventMessage : IEvent @@ -33,4 +33,6 @@ public class EventMessage : IEvent
public Guid? IdempotencyId { get; private set; } = Guid.NewGuid();
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
}

22
src/Core/Models/Data/EventTableEntity.cs

@ -28,6 +28,8 @@ public class EventTableEntity : TableEntity, IEvent @@ -28,6 +28,8 @@ public class EventTableEntity : TableEntity, IEvent
ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser;
DomainName = e.DomainName;
SecretId = e.SecretId;
ServiceAccountId = e.ServiceAccountId;
}
public DateTime Date { get; set; }
@ -48,6 +50,8 @@ public class EventTableEntity : TableEntity, IEvent @@ -48,6 +50,8 @@ public class EventTableEntity : TableEntity, IEvent
public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; }
public string DomainName { get; set; }
public Guid? SecretId { get; set; }
public Guid? ServiceAccountId { get; set; }
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
@ -154,6 +158,24 @@ public class EventTableEntity : TableEntity, IEvent @@ -154,6 +158,24 @@ public class EventTableEntity : TableEntity, IEvent
});
}
if (e.OrganizationId.HasValue && e.ServiceAccountId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"ServiceAccountId={e.ServiceAccountId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
if (e.SecretId.HasValue)
{
entities.Add(new EventTableEntity(e)
{
PartitionKey = pKey,
RowKey = $"SecretId={e.CipherId}__Date={dateKey}__Uniquifier={uniquifier}"
});
}
return entities;
}

2
src/Core/Models/Data/IEvent.cs

@ -22,4 +22,6 @@ public interface IEvent @@ -22,4 +22,6 @@ public interface IEvent
DateTime Date { get; set; }
EventSystemUser? SystemUser { get; set; }
string DomainName { get; set; }
Guid? SecretId { get; set; }
Guid? ServiceAccountId { get; set; }
}

2
src/Core/Services/IEventService.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.Services;
@ -25,4 +26,5 @@ public interface IEventService @@ -25,4 +26,5 @@ public interface IEventService
Task LogProviderOrganizationEventAsync(ProviderOrganization providerOrganization, EventType type, DateTime? date = null);
Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, DateTime? date = null);
Task LogOrganizationDomainEventAsync(OrganizationDomain organizationDomain, EventType type, EventSystemUser systemUser, DateTime? date = null);
Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null);
}

24
src/Core/Services/Implementations/EventService.cs

@ -5,6 +5,7 @@ using Bit.Core.Enums; @@ -5,6 +5,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.Settings;
namespace Bit.Core.Services;
@ -391,6 +392,25 @@ public class EventService : IEventService @@ -391,6 +392,25 @@ public class EventService : IEventService
await _eventWriteService.CreateAsync(e);
}
public async Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type, DateTime? date = null)
{
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
if (!CanUseEvents(orgAbilities, secret.OrganizationId))
{
return;
}
var e = new EventMessage(_currentContext)
{
OrganizationId = secret.OrganizationId,
Type = type,
SecretId = secret.Id,
ServiceAccountId = serviceAccountId,
Date = date.GetValueOrDefault(DateTime.UtcNow)
};
await _eventWriteService.CreateAsync(e);
}
private async Task<Guid?> GetProviderIdAsync(Guid? orgId)
{
if (_currentContext == null || !orgId.HasValue)
@ -414,12 +434,12 @@ public class EventService : IEventService @@ -414,12 +434,12 @@ public class EventService : IEventService
private bool CanUseEvents(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
{
return orgAbilities != null && orgAbilities.ContainsKey(orgId) &&
orgAbilities[orgId].Enabled && orgAbilities[orgId].UseEvents;
orgAbilities[orgId].Enabled && orgAbilities[orgId].UseEvents;
}
private bool CanUseProviderEvents(IDictionary<Guid, ProviderAbility> providerAbilities, Guid providerId)
{
return providerAbilities != null && providerAbilities.ContainsKey(providerId) &&
providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents;
providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents;
}
}

6
src/Core/Services/NoopImplementations/NoopEventService.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using Bit.Core.Entities;
using Bit.Core.Entities.Provider;
using Bit.Core.Enums;
using Bit.Core.SecretsManager.Entities;
namespace Bit.Core.Services;
@ -107,4 +108,9 @@ public class NoopEventService : IEventService @@ -107,4 +108,9 @@ public class NoopEventService : IEventService
return Task.FromResult(0);
}
public Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type,
DateTime? date = null)
{
return Task.FromResult(0);
}
}

12
src/Sql/dbo/Stored Procedures/Event_Create.sql

@ -17,7 +17,9 @@ @@ -17,7 +17,9 @@
@IpAddress VARCHAR(50),
@Date DATETIME2(7),
@SystemUser TINYINT = null,
@DomainName VARCHAR(256)
@DomainName VARCHAR(256),
@SecretId UNIQUEIDENTIFIER = null,
@ServiceAccountId UNIQUEIDENTIFIER = null
AS
BEGIN
SET NOCOUNT ON
@ -42,7 +44,9 @@ BEGIN @@ -42,7 +44,9 @@ BEGIN
[IpAddress],
[Date],
[SystemUser],
[DomainName]
[DomainName],
[SecretId],
[ServiceAccountId]
)
VALUES
(
@ -64,6 +68,8 @@ BEGIN @@ -64,6 +68,8 @@ BEGIN
@IpAddress,
@Date,
@SystemUser,
@DomainName
@DomainName,
@SecretId,
@ServiceAccountId
)
END

4
src/Sql/dbo/Tables/Event.sql

@ -17,7 +17,9 @@ @@ -17,7 +17,9 @@
[ProviderUserId] UNIQUEIDENTIFIER NULL,
[ProviderOrganizationId] UNIQUEIDENTIFIER NULL,
[SystemUser] TINYINT NULL,
[DomainName] VARCHAR(256) NULL
[DomainName] VARCHAR(256) NULL,
[SecretId] UNIQUEIDENTIFIER NULL,
[ServiceAccountId] UNIQUEIDENTIFIER NULL,
CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
);

100
util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql

@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
IF COL_LENGTH('[dbo].[Event]', 'SecretId') IS NULL
BEGIN
ALTER TABLE
[dbo].[Event]
ADD
[SecretId] UNIQUEIDENTIFIER NULL
END
GO
IF COL_LENGTH('[dbo].[Event]', 'ServiceAccountId') IS NULL
BEGIN
ALTER TABLE
[dbo].[Event]
ADD
[ServiceAccountId] UNIQUEIDENTIFIER NULL
END
GO
IF OBJECT_ID('[dbo].[EventView]') IS NOT NULL
BEGIN
EXECUTE sp_refreshsqlmodule N'[dbo].[EventView]';
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Event_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@Type INT,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@InstallationId UNIQUEIDENTIFIER,
@ProviderId UNIQUEIDENTIFIER,
@CipherId UNIQUEIDENTIFIER,
@CollectionId UNIQUEIDENTIFIER,
@PolicyId UNIQUEIDENTIFIER,
@GroupId UNIQUEIDENTIFIER,
@OrganizationUserId UNIQUEIDENTIFIER,
@ProviderUserId UNIQUEIDENTIFIER,
@ProviderOrganizationId UNIQUEIDENTIFIER = null,
@ActingUserId UNIQUEIDENTIFIER,
@DeviceType SMALLINT,
@IpAddress VARCHAR(50),
@Date DATETIME2(7),
@SystemUser TINYINT = null,
@DomainName VARCHAR(256),
@SecretId UNIQUEIDENTIFIER = null,
@ServiceAccountId UNIQUEIDENTIFIER = null
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Event]
(
[Id],
[Type],
[UserId],
[OrganizationId],
[InstallationId],
[ProviderId],
[CipherId],
[CollectionId],
[PolicyId],
[GroupId],
[OrganizationUserId],
[ProviderUserId],
[ProviderOrganizationId],
[ActingUserId],
[DeviceType],
[IpAddress],
[Date],
[SystemUser],
[DomainName],
[SecretId],
[ServiceAccountId]
)
VALUES
(
@Id,
@Type,
@UserId,
@OrganizationId,
@InstallationId,
@ProviderId,
@CipherId,
@CollectionId,
@PolicyId,
@GroupId,
@OrganizationUserId,
@ProviderUserId,
@ProviderOrganizationId,
@ActingUserId,
@DeviceType,
@IpAddress,
@Date,
@SystemUser,
@DomainName,
@SecretId,
@ServiceAccountId
)
END
GO

2138
util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs generated

File diff suppressed because it is too large Load Diff

36
util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.cs

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
public partial class SecretsManagerEvent : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "SecretId",
table: "Event",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
migrationBuilder.AddColumn<Guid>(
name: "ServiceAccountId",
table: "Event",
type: "char(36)",
nullable: true,
collation: "ascii_general_ci");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SecretId",
table: "Event");
migrationBuilder.DropColumn(
name: "ServiceAccountId",
table: "Event");
}
}

6
util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs

@ -349,6 +349,12 @@ namespace Bit.MySqlMigrations.Migrations @@ -349,6 +349,12 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<Guid?>("ProviderUserId")
.HasColumnType("char(36)");
b.Property<Guid?>("SecretId")
.HasColumnType("char(36)");
b.Property<Guid?>("ServiceAccountId")
.HasColumnType("char(36)");
b.Property<byte?>("SystemUser")
.HasColumnType("tinyint unsigned");

2149
util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs generated

File diff suppressed because it is too large Load Diff

34
util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.cs

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
public partial class SecretsManagerEvent : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "SecretId",
table: "Event",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "ServiceAccountId",
table: "Event",
type: "uuid",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SecretId",
table: "Event");
migrationBuilder.DropColumn(
name: "ServiceAccountId",
table: "Event");
}
}

6
util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs

@ -353,6 +353,12 @@ namespace Bit.PostgresMigrations.Migrations @@ -353,6 +353,12 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<Guid?>("ProviderUserId")
.HasColumnType("uuid");
b.Property<Guid?>("SecretId")
.HasColumnType("uuid");
b.Property<Guid?>("ServiceAccountId")
.HasColumnType("uuid");
b.Property<byte?>("SystemUser")
.HasColumnType("smallint");

2136
util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs generated

File diff suppressed because it is too large Load Diff

34
util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.cs

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
public partial class SecretsManagerEvent : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "SecretId",
table: "Event",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "ServiceAccountId",
table: "Event",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SecretId",
table: "Event");
migrationBuilder.DropColumn(
name: "ServiceAccountId",
table: "Event");
}
}

6
util/SqliteMigrations/Migrations/DatabaseContextModelSnapshot.cs

@ -344,6 +344,12 @@ namespace Bit.SqliteMigrations.Migrations @@ -344,6 +344,12 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<Guid?>("ProviderUserId")
.HasColumnType("TEXT");
b.Property<Guid?>("SecretId")
.HasColumnType("TEXT");
b.Property<Guid?>("ServiceAccountId")
.HasColumnType("TEXT");
b.Property<byte?>("SystemUser")
.HasColumnType("INTEGER");

Loading…
Cancel
Save