mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 16:27:40 +00:00
Add dashboard-specific secret and secret import audit logs, re-touch audit logs v2 UI
This commit is contained in:
@@ -86,9 +86,15 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
integration: integration.integration,
|
||||
environment: integration.environment,
|
||||
secretPath,
|
||||
url: integration.url,
|
||||
app: integration.app,
|
||||
appId: integration.appId,
|
||||
targetEnvironment: integration.targetEnvironment,
|
||||
targetEnvironmentId: integration.targetEnvironmentId
|
||||
targetEnvironmentId: integration.targetEnvironmentId,
|
||||
targetService: integration.targetService,
|
||||
targetServiceId: integration.targetServiceId,
|
||||
path: integration.path,
|
||||
region: integration.region
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -192,9 +198,15 @@ export const deleteIntegration = async (req: Request, res: Response) => {
|
||||
integration: integration.integration,
|
||||
environment: integration.environment,
|
||||
secretPath: integration.secretPath,
|
||||
url: integration.url,
|
||||
app: integration.app,
|
||||
appId: integration.appId,
|
||||
targetEnvironment: integration.targetEnvironment,
|
||||
targetEnvironmentId: integration.targetEnvironmentId
|
||||
targetEnvironmentId: integration.targetEnvironmentId,
|
||||
targetService: integration.targetService,
|
||||
targetServiceId: integration.targetServiceId,
|
||||
path: integration.path,
|
||||
region: integration.region
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import { Key } from "../../models";
|
||||
import { findMembership } from "../../helpers/membership";
|
||||
import { EventType } from "../../ee/models";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
|
||||
/**
|
||||
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
|
||||
@@ -44,7 +47,7 @@ export const uploadKey = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const getLatestKey = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
||||
// get latest key
|
||||
const latestKey = await Key.find({
|
||||
workspace: workspaceId,
|
||||
@@ -58,6 +61,18 @@ export const getLatestKey = async (req: Request, res: Response) => {
|
||||
|
||||
if (latestKey.length > 0) {
|
||||
resObj["latestKey"] = latestKey[0];
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
keyId: latestKey[0]._id.toString()
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return res.status(200).send(resObj);
|
||||
|
||||
@@ -4,6 +4,8 @@ import SecretImport from "../../models/secretImports";
|
||||
import { getAllImportedSecrets } from "../../services/SecretImportService";
|
||||
import { BadRequestError } from "../../utils/errors";
|
||||
import { ADMIN, MEMBER } from "../../variables";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import { EventType } from "../../ee/models";
|
||||
|
||||
export const createSecretImport = async (req: Request, res: Response) => {
|
||||
const { workspaceId, environment, folderId, secretImport } = req.body;
|
||||
@@ -20,7 +22,24 @@ export const createSecretImport = async (req: Request, res: Response) => {
|
||||
folderId,
|
||||
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
|
||||
});
|
||||
|
||||
await doc.save();
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment,
|
||||
secretImportId: doc._id.toString(),
|
||||
folderId: doc.folderId.toString(),
|
||||
importEnvironment: secretImport.environment,
|
||||
importSecretPath: secretImport.secretPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: doc.workspace
|
||||
}
|
||||
);
|
||||
return res.status(200).json({ message: "successfully created secret import" });
|
||||
}
|
||||
|
||||
@@ -30,11 +49,29 @@ export const createSecretImport = async (req: Request, res: Response) => {
|
||||
if (doesImportExist) {
|
||||
throw BadRequestError({ message: "Secret import already exist" });
|
||||
}
|
||||
|
||||
importSecDoc.imports.push({
|
||||
environment: secretImport.environment,
|
||||
secretPath: secretImport.secretPath
|
||||
});
|
||||
await importSecDoc.save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment,
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId: importSecDoc.folderId.toString(),
|
||||
importEnvironment: secretImport.environment,
|
||||
importSecretPath: secretImport.secretPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
return res.status(200).json({ message: "successfully created secret import" });
|
||||
};
|
||||
|
||||
@@ -56,6 +93,21 @@ export const updateSecretImport = async (req: Request, res: Response) => {
|
||||
|
||||
importSecDoc.imports = secretImports;
|
||||
await importSecDoc.save();
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment: importSecDoc.environment,
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId: importSecDoc.folderId.toString(),
|
||||
numberOfImports: secretImports.length
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
return res.status(200).json({ message: "successfully updated secret import" });
|
||||
};
|
||||
|
||||
@@ -77,6 +129,24 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
|
||||
!(environment === secretImportEnv && secretPath === secretImportPath)
|
||||
);
|
||||
await importSecDoc.save();
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment: importSecDoc.environment,
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId: importSecDoc.folderId.toString(),
|
||||
importEnvironment: secretImportEnv,
|
||||
importSecretPath: secretImportPath
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).json({ message: "successfully delete secret import" });
|
||||
};
|
||||
|
||||
@@ -111,6 +181,22 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
|
||||
return res.status(200).json({ secrets: [] });
|
||||
}
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_SECRET_IMPORTS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretImportId: importSecDoc._id.toString(),
|
||||
folderId,
|
||||
numberOfImports: importSecDoc.imports.length
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: importSecDoc.workspace
|
||||
}
|
||||
);
|
||||
|
||||
const secrets = await getAllImportedSecrets(workspaceId, environment, folderId);
|
||||
return res.status(200).json({ secrets });
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Types } from "mongoose";
|
||||
import { Request, Response } from "express";
|
||||
import { ISecret, Secret, ServiceTokenData } from "../../models";
|
||||
import { IAction, SecretVersion } from "../../ee/models";
|
||||
import { IAction, SecretVersion, EventType, AuditLog } from "../../ee/models";
|
||||
import {
|
||||
ACTION_ADD_SECRETS,
|
||||
ACTION_DELETE_SECRETS,
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
|
||||
import { EventService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { EELogService, EESecretService } from "../../ee/services";
|
||||
import { EELogService, EESecretService, EEAuditLogService } from "../../ee/services";
|
||||
import { SecretService, TelemetryService } from "../../services";
|
||||
import { getUserAgentType } from "../../utils/posthog";
|
||||
import { PERMISSION_WRITE_SECRETS } from "../../variables";
|
||||
@@ -56,12 +56,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
environment: string;
|
||||
requests: BatchSecretRequest[];
|
||||
} = req.body;
|
||||
|
||||
let secretPath = req.body.secretPath as string;
|
||||
let folderId = req.body.folderId as string;
|
||||
|
||||
|
||||
const createSecrets: BatchSecret[] = [];
|
||||
const updateSecrets: BatchSecret[] = [];
|
||||
const deleteSecrets: Types.ObjectId[] = [];
|
||||
const deleteSecrets: { _id: Types.ObjectId, secretName: string; }[] = [];
|
||||
const actions: IAction[] = [];
|
||||
|
||||
// get secret blind index salt
|
||||
@@ -133,7 +134,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
});
|
||||
break;
|
||||
case "DELETE":
|
||||
deleteSecrets.push(new Types.ObjectId(request.secret._id));
|
||||
deleteSecrets.push({ _id: new Types.ObjectId(request.secret._id), secretName: request.secret.secretName });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -153,7 +154,31 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
const auditLogs = await Promise.all(
|
||||
createdSecrets.map((secret, index) => {
|
||||
return EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.CREATE_SECRET,
|
||||
metadata: {
|
||||
environment: secret.environment,
|
||||
secretPath: secretPath ?? "/",
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: createSecrets[index].secretName,
|
||||
secretVersion: secret.version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: secret.workspace
|
||||
},
|
||||
false
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
await AuditLog.insertMany(auditLogs);
|
||||
|
||||
const addAction = (await EELogService.createAction({
|
||||
name: ACTION_ADD_SECRETS,
|
||||
userId: req.user?._id,
|
||||
@@ -252,6 +277,30 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
$in: updateSecrets.map((u) => new Types.ObjectId(u._id))
|
||||
}
|
||||
});
|
||||
|
||||
const auditLogs = await Promise.all(
|
||||
updateSecrets.map((secret) => {
|
||||
return EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.UPDATE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: secretPath ?? "/",
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secret.secretName,
|
||||
secretVersion: listedSecretsObj[secret._id.toString()].version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
false
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
await AuditLog.insertMany(auditLogs);
|
||||
|
||||
const updateAction = (await EELogService.createAction({
|
||||
name: ACTION_UPDATE_SECRETS,
|
||||
@@ -279,21 +328,60 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
|
||||
// handle delete secrets
|
||||
if (deleteSecrets.length > 0) {
|
||||
const deleteSecretIds: Types.ObjectId[] = deleteSecrets.map((s) => s._id);
|
||||
|
||||
const deletedSecretsObj = (await Secret.find({
|
||||
_id: {
|
||||
$in: deleteSecretIds
|
||||
}
|
||||
}))
|
||||
.reduce(
|
||||
(obj: any, secret: ISecret) => ({
|
||||
...obj,
|
||||
[secret._id.toString()]: secret
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
await Secret.deleteMany({
|
||||
_id: {
|
||||
$in: deleteSecrets
|
||||
$in: deleteSecretIds
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
await EESecretService.markDeletedSecretVersions({
|
||||
secretIds: deleteSecrets
|
||||
secretIds: deleteSecretIds
|
||||
});
|
||||
|
||||
const auditLogs = await Promise.all(
|
||||
deleteSecrets.map((secret) => {
|
||||
return EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.DELETE_SECRET,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath: secretPath ?? "/",
|
||||
secretId: secret._id.toString(),
|
||||
secretKey: secret.secretName,
|
||||
secretVersion: deletedSecretsObj[secret._id.toString()].version
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
},
|
||||
false
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
await AuditLog.insertMany(auditLogs);
|
||||
|
||||
const deleteAction = (await EELogService.createAction({
|
||||
name: ACTION_DELETE_SECRETS,
|
||||
userId: req.user._id,
|
||||
workspaceId: new Types.ObjectId(workspaceId),
|
||||
secretIds: deleteSecrets
|
||||
secretIds: deleteSecretIds
|
||||
})) as IAction;
|
||||
actions.push(deleteAction);
|
||||
|
||||
@@ -351,7 +439,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
if (deleteSecrets.length > 0) {
|
||||
resObj["deletedSecrets"] = deleteSecrets.map((d) => d.toString());
|
||||
resObj["deletedSecrets"] = deleteSecrets.map((d) => d._id.toString());
|
||||
}
|
||||
|
||||
return res.status(200).send(resObj);
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
import { pushKeys } from "../../helpers/key";
|
||||
import { EventService, TelemetryService } from "../../services";
|
||||
import { eventPushSecrets } from "../../events";
|
||||
import { EEAuditLogService } from "../../ee/services";
|
||||
import { EventType } from "../../ee/models";
|
||||
|
||||
interface V2PushSecret {
|
||||
type: string; // personal or shared
|
||||
@@ -180,16 +182,30 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
|
||||
}
|
||||
*/
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
||||
const key = await Key.findOne({
|
||||
workspace: workspaceId,
|
||||
receiver: req.user._id
|
||||
}).populate("sender", "+publicKey");
|
||||
|
||||
if (!key) throw new Error("Failed to find workspace key");
|
||||
|
||||
await EEAuditLogService.createAuditLog(
|
||||
req.authData,
|
||||
{
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
keyId: key._id.toString()
|
||||
}
|
||||
},
|
||||
{
|
||||
workspaceId: new Types.ObjectId(workspaceId)
|
||||
}
|
||||
);
|
||||
|
||||
return res.status(200).json(key);
|
||||
};
|
||||
|
||||
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
|
||||
const { workspaceId } = req.params;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export enum EventType {
|
||||
CREATE_SECRET = "create-secret",
|
||||
UPDATE_SECRET = "update-secret",
|
||||
DELETE_SECRET = "delete-secret",
|
||||
GET_WORKSPACE_KEY = "get-workspace-key",
|
||||
AUTHORIZE_INTEGRATION = "authorize-integration",
|
||||
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
|
||||
CREATE_INTEGRATION = "create-integration",
|
||||
@@ -36,5 +37,9 @@ export enum EventType {
|
||||
DELETE_FOLDER = "delete-folder",
|
||||
CREATE_WEBHOOK = "create-webhook",
|
||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||
DELETE_WEBHOOK = "delete-webhook"
|
||||
DELETE_WEBHOOK = "delete-webhook",
|
||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||
DELETE_SECRET_IMPORT = "delete-secret-import",
|
||||
}
|
||||
@@ -80,17 +80,24 @@ interface DeleteSecretEvent {
|
||||
}
|
||||
}
|
||||
|
||||
interface GetWorkspaceKeyEvent {
|
||||
type: EventType.GET_WORKSPACE_KEY,
|
||||
metadata: {
|
||||
keyId: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface AuthorizeIntegrationEvent {
|
||||
type: EventType.AUTHORIZE_INTEGRATION;
|
||||
metadata: {
|
||||
integration: string; // TODO: fix type
|
||||
integration: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UnauthorizeIntegrationEvent {
|
||||
type: EventType.UNAUTHORIZE_INTEGRATION;
|
||||
metadata: {
|
||||
integration: string; // TODO: fix type
|
||||
integration: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,9 +108,15 @@ interface CreateIntegrationEvent {
|
||||
integration: string; // TODO: fix type
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
url?: string;
|
||||
app?: string;
|
||||
appId?: string;
|
||||
targetEnvironment?: string;
|
||||
targetEnvironmentId?: string; // TODO: consider adding other vars
|
||||
targetEnvironmentId?: string;
|
||||
targetService?: string;
|
||||
targetServiceId?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,9 +127,15 @@ interface DeleteIntegrationEvent {
|
||||
integration: string; // TODO: fix type
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
url?: string;
|
||||
app?: string;
|
||||
appId?: string;
|
||||
targetEnvironment?: string;
|
||||
targetEnvironmentId?: string;
|
||||
targetService?: string;
|
||||
targetServiceId?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,12 +294,55 @@ interface DeleteWebhookEvent {
|
||||
}
|
||||
}
|
||||
|
||||
interface GetSecretImportsEvent {
|
||||
type: EventType.GET_SECRET_IMPORTS,
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
numberOfImports: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateSecretImportEvent {
|
||||
type: EventType.CREATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
importEnvironment: string;
|
||||
importSecretPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface UpdateSecretImportEvent {
|
||||
type: EventType.UPDATE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
numberOfImports: number;
|
||||
}
|
||||
}
|
||||
|
||||
interface DeleteSecretImportEvent {
|
||||
type: EventType.DELETE_SECRET_IMPORT,
|
||||
metadata: {
|
||||
environment: string;
|
||||
secretImportId: string;
|
||||
folderId: string;
|
||||
importEnvironment: string;
|
||||
importSecretPath: string;
|
||||
}
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| GetSecretsEvent
|
||||
| GetSecretEvent
|
||||
| CreateSecretEvent
|
||||
| UpdateSecretEvent
|
||||
| DeleteSecretEvent
|
||||
| GetWorkspaceKeyEvent
|
||||
| AuthorizeIntegrationEvent
|
||||
| UnauthorizeIntegrationEvent
|
||||
| CreateIntegrationEvent
|
||||
@@ -300,4 +362,8 @@ export type Event =
|
||||
| DeleteFolderEvent
|
||||
| CreateWebhookEvent
|
||||
| UpdateWebhookStatusEvent
|
||||
| DeleteWebhookEvent;
|
||||
| DeleteWebhookEvent
|
||||
| GetSecretImportsEvent
|
||||
| CreateSecretImportEvent
|
||||
| UpdateSecretImportEvent
|
||||
| DeleteSecretImportEvent;
|
||||
@@ -16,7 +16,7 @@ type ValidEventScope =
|
||||
| Required<EventScope>
|
||||
|
||||
export default class EEAuditLogService {
|
||||
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope) {
|
||||
static async createAuditLog(authData: AuthData, event: Event, eventScope: ValidEventScope, shouldSave: boolean = true) {
|
||||
|
||||
const MS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
@@ -39,7 +39,11 @@ export default class EEAuditLogService {
|
||||
userAgent: authData.userAgent,
|
||||
userAgentType: authData.userAgentType,
|
||||
expiresAt: new Date(Date.now() + ttl)
|
||||
}).save();
|
||||
});
|
||||
|
||||
if (shouldSave) {
|
||||
await auditLog.save();
|
||||
}
|
||||
|
||||
return auditLog;
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -30,6 +30,7 @@ export interface BatchSecretRequest {
|
||||
export interface BatchSecret {
|
||||
_id: string;
|
||||
type: "shared" | "personal";
|
||||
secretName: string;
|
||||
secretBlindIndex: string;
|
||||
secretKeyCiphertext: string;
|
||||
secretKeyIV: string;
|
||||
|
||||
Reference in New Issue
Block a user