mirror of
https://github.com/bitwarden/server.git
synced 2024-12-23 17:07:42 +01:00
[SM-389] Event log for service account (#2674)
This commit is contained in:
parent
4643f5960e
commit
64e0a981c9
@ -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
|
||||
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; }
|
||||
}
|
||||
|
@ -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;
|
||||
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;
|
||||
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;
|
||||
_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
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
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()
|
||||
{
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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
|
||||
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);
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task LogServiceAccountSecretEventAsync(Guid serviceAccountId, Secret secret, EventType type,
|
||||
DateTime? date = null)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
[IpAddress],
|
||||
[Date],
|
||||
[SystemUser],
|
||||
[DomainName]
|
||||
[DomainName],
|
||||
[SecretId],
|
||||
[ServiceAccountId]
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
@ -64,6 +68,8 @@ BEGIN
|
||||
@IpAddress,
|
||||
@Date,
|
||||
@SystemUser,
|
||||
@DomainName
|
||||
@DomainName,
|
||||
@SecretId,
|
||||
@ServiceAccountId
|
||||
)
|
||||
END
|
||||
|
@ -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
Normal file
100
util/Migrator/DbScripts/2023-02-16_00_SecretsManagerEvent.sql
Normal 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
|
2138
util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs
generated
Normal file
2138
util/MySqlMigrations/Migrations/20230213133250_SecretsManagerEvent.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
}
|
||||
}
|
@ -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
Normal file
2149
util/PostgresMigrations/Migrations/20230213133239_SecretsManagerEvent.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
}
|
||||
}
|
@ -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
Normal file
2136
util/SqliteMigrations/Migrations/20230213133244_SecretsManagerEvent.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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");
|
||||
}
|
||||
}
|
@ -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…
Reference in New Issue
Block a user