feat(secret-approval): added new audit log and subscription on policy creation

This commit is contained in:
Akhil Mohan
2023-10-05 20:44:16 +05:30
parent 7caac2e64c
commit faacb75034
12 changed files with 374 additions and 259 deletions
@@ -9,6 +9,8 @@ import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { ISecretApprovalPolicy, SecretApprovalPolicy } from "../../models/secretApprovalPolicy";
import { performSecretApprovalRequestMerge } from "../../services/SecretApprovalService";
import { Types } from "mongoose";
import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models";
export const getSecretApprovalRequests = async (req: Request, res: Response) => {
const {
@@ -31,6 +33,7 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
{
$match: query
},
{ $sort: { createdAt: -1 } },
{
$lookup: {
from: SecretApprovalPolicy.collection.name,
@@ -234,5 +237,37 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
{ status, statusChangeBy: membership._id },
{ new: true }
);
if (status === "close") {
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.SECRET_APPROVAL_CLOSED,
metadata: {
closedBy: membership._id.toString(),
secretApprovalRequestId: id,
secretApprovalRequestSlug: secretApprovalRequest.slug
}
},
{
workspaceId: secretApprovalRequest.workspace
}
);
} else {
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.SECRET_APPROVAL_REOPENED,
metadata: {
reopenedBy: membership._id.toString(),
secretApprovalRequestId: id,
secretApprovalRequestSlug: secretApprovalRequest.slug
}
},
{
workspaceId: secretApprovalRequest.workspace
}
);
}
return res.send({ approval: updatedRequest });
};
+132 -112
View File
@@ -595,33 +595,37 @@ export const createSecret = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Create
});
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy && membership && type !== "personal") {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.CREATE]: [
{
secretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
authData:req.authData,
data: {
[CommitType.CREATE]: [
{
secretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
}
const secret = await SecretService.createSecret({
@@ -700,35 +704,38 @@ export const updateSecretByName = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Edit
});
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy && membership && type !== "personal") {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.UPDATE]: [
{
secretName,
newSecretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
tags,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
authData: req.authData,
data: {
[CommitType.UPDATE]: [
{
secretName,
newSecretName,
secretValueCiphertext,
secretValueIV,
secretValueTag,
tags,
secretCommentIV,
secretCommentTag,
secretCommentCiphertext,
skipMultilineEncoding,
secretKeyTag,
secretKeyCiphertext,
secretKeyIV
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
}
const secret = await SecretService.updateSecret({
@@ -784,23 +791,26 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Delete
});
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy && membership && type !== "personal") {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: [
{
secretName
}
]
}
});
return res.send({ approval: secretApprovalRequest });
if (membership && type !== "personal") {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
authData: req.authData,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: [
{
secretName
}
]
}
});
return res.send({ approval: secretApprovalRequest });
}
}
const { secret } = await SecretService.deleteSecret({
@@ -838,19 +848,22 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Create
});
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy && membership) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
authData: req.authData,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.CREATE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
}
}
const createdSecrets = await SecretService.createSecretBatch({
@@ -879,19 +892,22 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Edit
});
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy && membership) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
}
}
const updatedSecrets = await SecretService.updateSecretBatch({
@@ -920,19 +936,23 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => {
secretAction: ProjectPermissionActions.Delete
});
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy && membership) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
}
});
return res.send({ approval: secretApprovalRequest });
if (membership) {
const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath);
if (secretApprovalPolicy) {
const secretApprovalRequest = await generateSecretApprovalRequest({
workspaceId,
environment,
secretPath,
policy: secretApprovalPolicy,
commiterMembershipId: membership._id.toString(),
data: {
[CommitType.DELETE]: secrets.filter(({ type }) => type === "shared")
},
authData: req.authData
});
return res.send({ approval: secretApprovalRequest });
}
}
const deletedSecrets = await SecretService.deleteSecretBatch({
+44 -40
View File
@@ -12,43 +12,47 @@ export enum UserAgentType {
}
export enum EventType {
GET_SECRETS = "get-secrets",
GET_SECRET = "get-secret",
REVEAL_SECRET = "reveal-secret",
CREATE_SECRET = "create-secret",
CREATE_SECRETS = "create-secrets",
UPDATE_SECRET = "update-secret",
UPDATE_SECRETS = "update-secrets",
DELETE_SECRET = "delete-secret",
DELETE_SECRETS = "delete-secrets",
GET_WORKSPACE_KEY = "get-workspace-key",
AUTHORIZE_INTEGRATION = "authorize-integration",
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
CREATE_INTEGRATION = "create-integration",
DELETE_INTEGRATION = "delete-integration",
ADD_TRUSTED_IP = "add-trusted-ip",
UPDATE_TRUSTED_IP = "update-trusted-ip",
DELETE_TRUSTED_IP = "delete-trusted-ip",
CREATE_SERVICE_TOKEN = "create-service-token", // v2
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
CREATE_SERVICE_TOKEN_V3 = "create-service-token-v3", // v3
UPDATE_SERVICE_TOKEN_V3 = "update-service-token-v3", // v3
DELETE_SERVICE_TOKEN_V3 = "delete-service-token-v3", // v3
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member",
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
CREATE_FOLDER = "create-folder",
UPDATE_FOLDER = "update-folder",
DELETE_FOLDER = "delete-folder",
CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
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",
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions"
}
GET_SECRETS = "get-secrets",
GET_SECRET = "get-secret",
REVEAL_SECRET = "reveal-secret",
CREATE_SECRET = "create-secret",
CREATE_SECRETS = "create-secrets",
UPDATE_SECRET = "update-secret",
UPDATE_SECRETS = "update-secrets",
DELETE_SECRET = "delete-secret",
DELETE_SECRETS = "delete-secrets",
GET_WORKSPACE_KEY = "get-workspace-key",
AUTHORIZE_INTEGRATION = "authorize-integration",
UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
CREATE_INTEGRATION = "create-integration",
DELETE_INTEGRATION = "delete-integration",
ADD_TRUSTED_IP = "add-trusted-ip",
UPDATE_TRUSTED_IP = "update-trusted-ip",
DELETE_TRUSTED_IP = "delete-trusted-ip",
CREATE_SERVICE_TOKEN = "create-service-token", // v2
DELETE_SERVICE_TOKEN = "delete-service-token", // v2
CREATE_SERVICE_TOKEN_V3 = "create-service-token-v3", // v3
UPDATE_SERVICE_TOKEN_V3 = "update-service-token-v3", // v3
DELETE_SERVICE_TOKEN_V3 = "delete-service-token-v3", // v3
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member",
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
CREATE_FOLDER = "create-folder",
UPDATE_FOLDER = "update-folder",
DELETE_FOLDER = "delete-folder",
CREATE_WEBHOOK = "create-webhook",
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
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",
UPDATE_USER_WORKSPACE_ROLE = "update-user-workspace-role",
UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS = "update-user-workspace-denied-permissions",
SECRET_APPROVAL_MERGED = "secret-approval-merged",
SECRET_APPROVAL_REQUEST = "secret-approval-request",
SECRET_APPROVAL_CLOSED = "secret-approval-closed",
SECRET_APPROVAL_REOPENED = "secret-approval-reopened"
}
+78 -39
View File
@@ -437,43 +437,82 @@ interface UpdateUserDeniedPermissions {
}[]
}
}
interface SecretApprovalMerge {
type: EventType.SECRET_APPROVAL_MERGED;
metadata: {
mergedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
| CreateSecretEvent
| CreateSecretBatchEvent
| UpdateSecretEvent
| UpdateSecretBatchEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
| GetWorkspaceKeyEvent
| AuthorizeIntegrationEvent
| UnauthorizeIntegrationEvent
| CreateIntegrationEvent
| DeleteIntegrationEvent
| AddTrustedIPEvent
| UpdateTrustedIPEvent
| DeleteTrustedIPEvent
| CreateServiceTokenEvent
| DeleteServiceTokenEvent
| CreateServiceTokenV3Event
| UpdateServiceTokenV3Event
| DeleteServiceTokenV3Event
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
| AddWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
| DeleteFolderEvent
| CreateWebhookEvent
| UpdateWebhookStatusEvent
| DeleteWebhookEvent
| GetSecretImportsEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent
| UpdateUserRole
| UpdateUserDeniedPermissions;
interface SecretApprovalClosed {
type: EventType.SECRET_APPROVAL_CLOSED;
metadata: {
closedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalReopened {
type: EventType.SECRET_APPROVAL_REOPENED;
metadata: {
reopenedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
interface SecretApprovalRequest {
type: EventType.SECRET_APPROVAL_REQUEST;
metadata: {
committedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
| CreateSecretEvent
| CreateSecretBatchEvent
| UpdateSecretEvent
| UpdateSecretBatchEvent
| DeleteSecretEvent
| DeleteSecretBatchEvent
| GetWorkspaceKeyEvent
| AuthorizeIntegrationEvent
| UnauthorizeIntegrationEvent
| CreateIntegrationEvent
| DeleteIntegrationEvent
| AddTrustedIPEvent
| UpdateTrustedIPEvent
| DeleteTrustedIPEvent
| CreateServiceTokenEvent
| DeleteServiceTokenEvent
| CreateServiceTokenV3Event
| UpdateServiceTokenV3Event
| DeleteServiceTokenV3Event
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent
| AddWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent
| CreateFolderEvent
| UpdateFolderEvent
| DeleteFolderEvent
| CreateWebhookEvent
| UpdateWebhookStatusEvent
| DeleteWebhookEvent
| GetSecretImportsEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent
| UpdateUserRole
| UpdateUserDeniedPermissions
| SecretApprovalMerge
| SecretApprovalClosed
| SecretApprovalRequest
| SecretApprovalReopened;
@@ -1,4 +1,5 @@
import { Schema, Types, model } from "mongoose";
import { customAlphabet } from "nanoid";
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8 } from "../variables";
export enum ApprovalStatus {
@@ -13,6 +14,9 @@ export enum CommitType {
CREATE = "create"
}
const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const nanoId = customAlphabet(SLUG_ALPHABETS, 10);
export interface ISecretApprovalSecChange {
_id: Types.ObjectId;
version: number;
@@ -54,6 +58,7 @@ export type ISecretCommits<T = Types.ObjectId, J = Types.ObjectId> = Array<
export interface ISecretApprovalRequest {
_id: Types.ObjectId;
committer: Types.ObjectId;
slug: string;
statusChangeBy: Types.ObjectId;
reviewers: {
member: Types.ObjectId;
@@ -143,6 +148,10 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
required: true,
default: "root"
},
slug: {
type: String,
default: () => nanoId()
},
reviewers: {
type: [
{
+35 -59
View File
@@ -96,6 +96,7 @@ type TGenerateSecretApprovalRequestArg = {
[CommitType.DELETE]?: { secretName: string }[];
};
commiterMembershipId: string;
authData: AuthData;
};
export const generateSecretApprovalRequest = async ({
@@ -104,7 +105,8 @@ export const generateSecretApprovalRequest = async ({
secretPath,
policy,
data,
commiterMembershipId
commiterMembershipId,
authData
}: TGenerateSecretApprovalRequestArg) => {
// calculate folder id from secret path
let folderId = "root";
@@ -313,6 +315,22 @@ export const generateSecretApprovalRequest = async ({
committer: commiterMembershipId
});
await secretApprovalRequest.save();
await EEAuditLogService.createAuditLog(
authData,
{
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: commiterMembershipId,
secretApprovalRequestId: secretApprovalRequest._id.toString(),
secretApprovalRequestSlug: secretApprovalRequest.slug
}
},
{
workspaceId: secretApprovalRequest.workspace
}
);
return secretApprovalRequest;
};
@@ -436,26 +454,6 @@ export const performSecretApprovalRequestMerge = async (
})
)
});
// question to team where to keep secretKey
await EEAuditLogService.createAuditLog(
authData,
{
type: EventType.CREATE_SECRETS,
metadata: {
environment,
secretPath: "/",
secrets: newlyCreatedSecrets.map(({ version, _id }) => ({
secretId: _id.toString(),
secretKey: "",
secretVersion: version
}))
}
},
{
workspaceId
}
);
}
const secretUpdationCommits = secretApprovalRequest.commits.filter(
@@ -570,25 +568,6 @@ export const performSecretApprovalRequestMerge = async (
});
})
});
await EEAuditLogService.createAuditLog(
authData,
{
type: EventType.UPDATE_SECRETS,
metadata: {
environment,
secretPath: "/",
secrets: nonConflictSecrets.map(({ secret }) => ({
secretId: secret._id.toString(),
secretKey: "",
secretVersion: secret.version + 1
}))
}
},
{
workspaceId
}
);
}
const secretDeletionCommits = secretApprovalRequest.commits.filter(
@@ -614,25 +593,6 @@ export const performSecretApprovalRequestMerge = async (
await EESecretService.markDeletedSecretVersions({
secretIds: secretDeletionCommits.map(({ secret }) => secret._id)
});
await EEAuditLogService.createAuditLog(
authData,
{
type: EventType.DELETE_SECRETS,
metadata: {
environment,
secretPath: "/",
secrets: secretDeletionCommits.map(({ secret: { _id, version } }) => ({
secretId: _id.toString(),
secretKey: "",
secretVersion: version
}))
}
},
{
workspaceId
}
);
}
const updatedSecretApproval = await SecretApprovalRequest.findByIdAndUpdate(
@@ -671,5 +631,21 @@ export const performSecretApprovalRequestMerge = async (
folderId
});
// question to team where to keep secretKey
await EEAuditLogService.createAuditLog(
authData,
{
type: EventType.SECRET_APPROVAL_MERGED,
metadata: {
mergedBy: userMembershipId,
secretApprovalRequestId: id,
secretApprovalRequestSlug: secretApprovalRequest.slug
}
},
{
workspaceId
}
);
return updatedSecretApproval;
};