1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-22 12:15:36 +01:00

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

This commit is contained in:
Oscar Hinton 2023-02-24 16:44:33 +01:00 committed by GitHub
parent 4643f5960e
commit 64e0a981c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 6744 additions and 11 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,6 +28,8 @@ public class EventTableEntity : TableEntity, IEvent
ActingUserId = e.ActingUserId; ActingUserId = e.ActingUserId;
SystemUser = e.SystemUser; SystemUser = e.SystemUser;
DomainName = e.DomainName; DomainName = e.DomainName;
SecretId = e.SecretId;
ServiceAccountId = e.ServiceAccountId;
} }
public DateTime Date { get; set; } public DateTime Date { get; set; }
@ -48,6 +50,8 @@ public class EventTableEntity : TableEntity, IEvent
public Guid? ActingUserId { get; set; } public Guid? ActingUserId { get; set; }
public EventSystemUser? SystemUser { get; set; } public EventSystemUser? SystemUser { get; set; }
public string DomainName { 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) public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{ {
@ -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; return entities;
} }

View File

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

View File

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

View File

@ -5,6 +5,7 @@ using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Data.Organizations; using Bit.Core.Models.Data.Organizations;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.SecretsManager.Entities;
using Bit.Core.Settings; using Bit.Core.Settings;
namespace Bit.Core.Services; namespace Bit.Core.Services;
@ -391,6 +392,25 @@ public class EventService : IEventService
await _eventWriteService.CreateAsync(e); 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) private async Task<Guid?> GetProviderIdAsync(Guid? orgId)
{ {
if (_currentContext == null || !orgId.HasValue) if (_currentContext == null || !orgId.HasValue)
@ -414,12 +434,12 @@ public class EventService : IEventService
private bool CanUseEvents(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId) private bool CanUseEvents(IDictionary<Guid, OrganizationAbility> orgAbilities, Guid orgId)
{ {
return orgAbilities != null && orgAbilities.ContainsKey(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) private bool CanUseProviderEvents(IDictionary<Guid, ProviderAbility> providerAbilities, Guid providerId)
{ {
return providerAbilities != null && providerAbilities.ContainsKey(providerId) && return providerAbilities != null && providerAbilities.ContainsKey(providerId) &&
providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents; providerAbilities[providerId].Enabled && providerAbilities[providerId].UseEvents;
} }
} }

View File

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

View File

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

View File

@ -17,7 +17,9 @@
[ProviderUserId] UNIQUEIDENTIFIER NULL, [ProviderUserId] UNIQUEIDENTIFIER NULL,
[ProviderOrganizationId] UNIQUEIDENTIFIER NULL, [ProviderOrganizationId] UNIQUEIDENTIFIER NULL,
[SystemUser] TINYINT 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) CONSTRAINT [PK_Event] PRIMARY KEY CLUSTERED ([Id] ASC)
); );

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}

View File

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