And workspace-environment specific integrations syncs to secrets v3 endpoints, add PostHog

This commit is contained in:
Tuan Dang
2023-04-17 14:23:56 +03:00
parent 338d287d35
commit 763ec1aa0f
18 changed files with 281 additions and 294 deletions
@@ -56,7 +56,8 @@ export const createIntegration = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
EventService.handleEvent({ EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: integration.workspace.toString() workspaceId: integration.workspace,
environment: sourceEnvironment
}) })
}); });
} }
@@ -117,7 +118,8 @@ export const updateIntegration = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
EventService.handleEvent({ EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: integration.workspace.toString(), workspaceId: integration.workspace,
environment
}), }),
}); });
} }
@@ -1,5 +1,6 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Key, Secret } from '../../models'; import { Key, Secret } from '../../models';
import { import {
v1PushSecrets as push, v1PushSecrets as push,
@@ -84,7 +85,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
EventService.handleEvent({ EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId workspaceId: new Types.ObjectId(workspaceId),
environment
}) })
}); });
@@ -248,7 +248,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
// // trigger event - push secrets // // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId workspaceId: new Types.ObjectId(workspaceId)
}) })
}); });
@@ -404,7 +404,7 @@ export const createSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId workspaceId: new Types.ObjectId(workspaceId)
}) })
}); });
}, 5000); }, 5000);
@@ -471,9 +471,7 @@ export const createSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: 'secrets added', event: 'secrets added',
distinctId: TelemetryService.getDistinctId({ distinctId: TelemetryService.getDistinctId({
user: req.user, authData: req.authData
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}), }),
properties: { properties: {
numberOfSecrets: listOfSecretsToCreate.length, numberOfSecrets: listOfSecretsToCreate.length,
@@ -648,9 +646,7 @@ export const getSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: 'secrets pulled', event: 'secrets pulled',
distinctId: TelemetryService.getDistinctId({ distinctId: TelemetryService.getDistinctId({
user: req.user, authData: req.authData
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}), }),
properties: { properties: {
numberOfSecrets: secrets.length, numberOfSecrets: secrets.length,
@@ -837,7 +833,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
setTimeout(async () => { setTimeout(async () => {
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: key workspaceId: new Types.ObjectId(key)
}) })
}); });
}, 10000); }, 10000);
@@ -872,9 +868,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: 'secrets modified', event: 'secrets modified',
distinctId: TelemetryService.getDistinctId({ distinctId: TelemetryService.getDistinctId({
user: req.user, authData: req.authData
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}), }),
properties: { properties: {
numberOfSecrets: workspaceSecretObj[key].length, numberOfSecrets: workspaceSecretObj[key].length,
@@ -975,7 +969,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId: key workspaceId: new Types.ObjectId(key)
}) })
}); });
const deleteAction = await EELogService.createAction({ const deleteAction = await EELogService.createAction({
@@ -1008,9 +1002,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
postHogClient.capture({ postHogClient.capture({
event: 'secrets deleted', event: 'secrets deleted',
distinctId: TelemetryService.getDistinctId({ distinctId: TelemetryService.getDistinctId({
user: req.user, authData: req.authData
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}), }),
properties: { properties: {
numberOfSecrets: workspaceSecretObj[key].length, numberOfSecrets: workspaceSecretObj[key].length,
@@ -95,7 +95,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets // trigger event - push secrets
EventService.handleEvent({ EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
workspaceId workspaceId: new Types.ObjectId(workspaceId),
environment
}) })
}); });
+25 -95
View File
@@ -1,6 +1,11 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { SecretService, TelemetryService } from '../../services'; import {
SecretService,
TelemetryService,
EventService
} from '../../services';
import { eventPushSecrets } from '../../events';
import { getAuthDataPayloadIdObj } from '../../utils/auth'; import { getAuthDataPayloadIdObj } from '../../utils/auth';
import { BadRequestError } from '../../utils/errors'; import { BadRequestError } from '../../utils/errors';
@@ -20,25 +25,6 @@ export const getSecrets = async (req: Request, res: Response) => {
authData: req.authData authData: req.authData
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets pulled',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({ return res.status(200).send({
secrets secrets
}); });
@@ -62,25 +48,6 @@ export const getSecretByName = async (req: Request, res: Response) => {
type, type,
authData: req.authData authData: req.authData
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets pull',
distinctId: TelemetryService.getDistinctId({
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({ return res.status(200).send({
secret secret
@@ -120,24 +87,12 @@ export const createSecret = async (req: Request, res: Response) => {
secretValueTag secretValueTag
}); });
const postHogClient = TelemetryService.getPostHogClient(); await EventService.handleEvent({
if (postHogClient) { event: eventPushSecrets({
postHogClient.capture({ workspaceId: new Types.ObjectId(workspaceId),
event: 'secrets added', environment
distinctId: TelemetryService.getDistinctId({ })
user: req.user, });
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
const secretWithoutBlindIndex = secret.toObject(); const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex; delete secretWithoutBlindIndex.secretBlindIndex;
@@ -173,26 +128,13 @@ export const updateSecretByName = async (req: Request, res: Response) => {
secretValueIV, secretValueIV,
secretValueTag secretValueTag
}); });
await EventService.handleEvent({
const postHogClient = TelemetryService.getPostHogClient(); event: eventPushSecrets({
if (postHogClient) { workspaceId: new Types.ObjectId(workspaceId),
postHogClient.capture({ environment
event: 'secrets modified', })
distinctId: TelemetryService.getDistinctId({ });
user: req.user,
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({ return res.status(200).send({
secret secret
@@ -220,24 +162,12 @@ export const deleteSecretByName = async (req: Request, res: Response) => {
authData: req.authData authData: req.authData
}); });
const postHogClient = TelemetryService.getPostHogClient(); await EventService.handleEvent({
if (postHogClient) { event: eventPushSecrets({
postHogClient.capture({ workspaceId: new Types.ObjectId(workspaceId),
event: 'secrets deleted', environment
distinctId: TelemetryService.getDistinctId({ })
user: req.user, });
serviceAccount: req.serviceAccount,
serviceTokenData: req.serviceTokenData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: req.authData.authChannel,
userAgent: req.headers?.['user-agent']
}
});
}
return res.status(200).send({ return res.status(200).send({
secret secret
+6 -2
View File
@@ -1,3 +1,4 @@
import { Types } from 'mongoose';
import { import {
EVENT_PUSH_SECRETS, EVENT_PUSH_SECRETS,
EVENT_PULL_SECRETS EVENT_PULL_SECRETS
@@ -22,13 +23,16 @@ interface PushSecret {
* @returns * @returns
*/ */
const eventPushSecrets = ({ const eventPushSecrets = ({
workspaceId workspaceId,
environment
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
environment?: string;
}) => { }) => {
return ({ return ({
name: EVENT_PUSH_SECRETS, name: EVENT_PUSH_SECRETS,
workspaceId, workspaceId,
environment,
payload: { payload: {
} }
+4 -4
View File
@@ -151,7 +151,7 @@ const getSecretsHelper = async ({
workspaceId, workspaceId,
environment environment
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
environment: string; environment: string;
}) => { }) => {
const content = {} as any; const content = {} as any;
@@ -196,7 +196,7 @@ const getSecretsHelper = async ({
* @param {String} obj.workspaceId - id of workspace * @param {String} obj.workspaceId - id of workspace
* @returns {String} key - decrypted workspace key * @returns {String} key - decrypted workspace key
*/ */
const getKey = async ({ workspaceId }: { workspaceId: string }) => { const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => {
let key; let key;
try { try {
const botKey = await BotKey.findOne({ const botKey = await BotKey.findOne({
@@ -245,7 +245,7 @@ const encryptSymmetricHelper = async ({
workspaceId, workspaceId,
plaintext plaintext
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
plaintext: string; plaintext: string;
}) => { }) => {
@@ -282,7 +282,7 @@ const decryptSymmetricHelper = async ({
iv, iv,
tag tag
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
ciphertext: string; ciphertext: string;
iv: string; iv: string;
tag: string; tag: string;
+10 -4
View File
@@ -1,11 +1,13 @@
import { Bot, IBot } from '../models'; import { Types } from 'mongoose';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { Bot, IBot } from '../models';
import { EVENT_PUSH_SECRETS } from '../variables'; import { EVENT_PUSH_SECRETS } from '../variables';
import { IntegrationService } from '../services'; import { IntegrationService } from '../services';
interface Event { interface Event {
name: string; name: string;
workspaceId: string; workspaceId: Types.ObjectId;
environment?: string;
payload: any; payload: any;
} }
@@ -22,7 +24,10 @@ const handleEventHelper = async ({
}: { }: {
event: Event; event: Event;
}) => { }) => {
const { workspaceId } = event; const {
workspaceId,
environment
} = event;
// TODO: moduralize bot check into separate function // TODO: moduralize bot check into separate function
const bot = await Bot.findOne({ const bot = await Bot.findOne({
@@ -36,7 +41,8 @@ const handleEventHelper = async ({
switch (event.name) { switch (event.name) {
case EVENT_PUSH_SECRETS: case EVENT_PUSH_SECRETS:
IntegrationService.syncIntegrations({ IntegrationService.syncIntegrations({
workspaceId workspaceId,
environment
}); });
break; break;
} }
+14 -9
View File
@@ -217,14 +217,19 @@ const handleOAuthExchangeHelper = async ({
* @param {Object} obj.workspaceId - id of workspace * @param {Object} obj.workspaceId - id of workspace
*/ */
const syncIntegrationsHelper = async ({ const syncIntegrationsHelper = async ({
workspaceId workspaceId,
environment
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
environment?: string;
}) => { }) => {
let integrations; let integrations;
try { try {
integrations = await Integration.find({ integrations = await Integration.find({
workspace: workspaceId, workspace: workspaceId,
...(environment ? {
environment
} : {}),
isActive: true, isActive: true,
app: { $ne: null } app: { $ne: null }
}); });
@@ -234,7 +239,7 @@ const syncIntegrationsHelper = async ({
for await (const integration of integrations) { for await (const integration of integrations) {
// get workspace, environment (shared) secrets // get workspace, environment (shared) secrets
const secrets = await BotService.getSecrets({ // issue here? const secrets = await BotService.getSecrets({ // issue here?
workspaceId: integration.workspace.toString(), workspaceId: integration.workspace,
environment: integration.environment environment: integration.environment
}); });
@@ -281,7 +286,7 @@ const syncIntegrationsHelper = async ({
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
refreshToken = await BotService.decryptSymmetric({ refreshToken = await BotService.decryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace,
ciphertext: integrationAuth.refreshCiphertext as string, ciphertext: integrationAuth.refreshCiphertext as string,
iv: integrationAuth.refreshIV as string, iv: integrationAuth.refreshIV as string,
tag: integrationAuth.refreshTag as string tag: integrationAuth.refreshTag as string
@@ -318,7 +323,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
accessToken = await BotService.decryptSymmetric({ accessToken = await BotService.decryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace,
ciphertext: integrationAuth.accessCiphertext as string, ciphertext: integrationAuth.accessCiphertext as string,
iv: integrationAuth.accessIV as string, iv: integrationAuth.accessIV as string,
tag: integrationAuth.accessTag as string tag: integrationAuth.accessTag as string
@@ -340,7 +345,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) { if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
accessId = await BotService.decryptSymmetric({ accessId = await BotService.decryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace,
ciphertext: integrationAuth.accessIdCiphertext as string, ciphertext: integrationAuth.accessIdCiphertext as string,
iv: integrationAuth.accessIdIV as string, iv: integrationAuth.accessIdIV as string,
tag: integrationAuth.accessIdTag as string tag: integrationAuth.accessIdTag as string
@@ -386,7 +391,7 @@ const setIntegrationAuthRefreshHelper = async ({
if (!integrationAuth) throw new Error('Failed to find integration auth'); if (!integrationAuth) throw new Error('Failed to find integration auth');
const obj = await BotService.encryptSymmetric({ const obj = await BotService.encryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace,
plaintext: refreshToken plaintext: refreshToken
}); });
@@ -435,14 +440,14 @@ const setIntegrationAuthAccessHelper = async ({
if (!integrationAuth) throw new Error('Failed to find integration auth'); if (!integrationAuth) throw new Error('Failed to find integration auth');
const encryptedAccessTokenObj = await BotService.encryptSymmetric({ const encryptedAccessTokenObj = await BotService.encryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace,
plaintext: accessToken plaintext: accessToken
}); });
let encryptedAccessIdObj; let encryptedAccessIdObj;
if (accessId) { if (accessId) {
encryptedAccessIdObj = await BotService.encryptSymmetric({ encryptedAccessIdObj = await BotService.encryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace,
plaintext: accessId plaintext: accessId
}); });
} }
+138 -17
View File
@@ -6,21 +6,18 @@ import {
UpdateSecretParams, UpdateSecretParams,
DeleteSecretParams DeleteSecretParams
} from '../interfaces/services/SecretService'; } from '../interfaces/services/SecretService';
import {
AuthData
} from '../interfaces/middleware';
import { import {
User, User,
IUser,
ServiceAccount, ServiceAccount,
IServiceAccount,
ServiceTokenData, ServiceTokenData,
IServiceTokenData,
Secret, Secret,
ISecret, ISecret,
SecretBlindIndexData, SecretBlindIndexData,
} from '../models'; } from '../models';
import { import { SecretVersion } from '../ee/models';
IAction,
SecretVersion
} from '../ee/models';
import { import {
validateMembership validateMembership
} from '../helpers/membership'; } from '../helpers/membership';
@@ -55,8 +52,12 @@ import {
} from '../variables'; } from '../variables';
import crypto from 'crypto'; import crypto from 'crypto';
import * as argon2 from 'argon2'; import * as argon2 from 'argon2';
import { decryptSymmetric } from '../utils/crypto'; import {
encryptSymmetric,
decryptSymmetric
} from '../utils/crypto';
import { getEncryptionKey } from '../config'; import { getEncryptionKey } from '../config';
import { TelemetryService } from '../services';
import { import {
EESecretService, EESecretService,
EELogService EELogService
@@ -80,10 +81,7 @@ const validateClientForSecret = async ({
acceptedRoles, acceptedRoles,
requiredPermissions requiredPermissions
}: { }: {
authData: { authData: AuthData;
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
},
secretId: Types.ObjectId; secretId: Types.ObjectId;
acceptedRoles: Array<'admin' | 'member'>; acceptedRoles: Array<'admin' | 'member'>;
requiredPermissions: string[]; requiredPermissions: string[];
@@ -157,10 +155,7 @@ const validateClientForSecrets = async ({
secretIds, secretIds,
requiredPermissions requiredPermissions
}: { }: {
authData: { authData: AuthData;
authMode: string;
authPayload: IUser | IServiceAccount | IServiceTokenData;
},
secretIds: Types.ObjectId[]; secretIds: Types.ObjectId[];
requiredPermissions: string[]; requiredPermissions: string[];
}) => { }) => {
@@ -222,6 +217,39 @@ const validateClientForSecrets = async ({
}); });
} }
/**
* Create secret blind index data containing encrypted blind index salt
* for workspace with id [workspaceId]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId
*/
const createSecretBlindIndexDataHelper = async ({
workspaceId
}: {
workspaceId: Types.ObjectId;
}) => {
// initialize random blind index salt for workspace
const salt = crypto.randomBytes(16).toString('base64');
const {
ciphertext: encryptedSaltCiphertext,
iv: saltIV,
tag: saltTag
} = encryptSymmetric({
plaintext: salt,
key: getEncryptionKey()
});
const secretBlindIndexData = await new SecretBlindIndexData({
workspace: workspaceId,
encryptedSaltCiphertext,
saltIV,
saltTag
}).save();
return secretBlindIndexData;
}
/** /**
* Generate blind index for secret with name [secretName] * Generate blind index for secret with name [secretName]
* for workspace with id [workspaceId] * for workspace with id [workspaceId]
@@ -389,6 +417,24 @@ const createSecretHelper = async ({
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId workspaceId
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets added',
distinctId: TelemetryService.getDistinctId({
authData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: authData.authChannel,
userAgent: authData.authUserAgent
}
});
}
return secret; return secret;
} }
@@ -430,6 +476,7 @@ const getSecretsHelper = async ({
} }
})); }));
// (EE) create (audit) log
const action = await EELogService.createAction({ const action = await EELogService.createAction({
name: ACTION_READ_SECRETS, name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
@@ -445,6 +492,24 @@ const getSecretsHelper = async ({
ipAddress: authData.authIP ipAddress: authData.authIP
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets pulled',
distinctId: TelemetryService.getDistinctId({
authData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: authData.authChannel,
userAgent: authData.authUserAgent
}
});
}
return secrets; return secrets;
} }
@@ -501,6 +566,7 @@ const getSecretHelper = async ({
if (!secret) throw SecretNotFoundError(); if (!secret) throw SecretNotFoundError();
// (EE) create (audit) log
const action = await EELogService.createAction({ const action = await EELogService.createAction({
name: ACTION_READ_SECRETS, name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData), ...getAuthDataPayloadIdObj(authData),
@@ -516,6 +582,24 @@ const getSecretHelper = async ({
ipAddress: authData.authIP ipAddress: authData.authIP
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets pull',
distinctId: TelemetryService.getDistinctId({
authData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: authData.authChannel,
userAgent: authData.authUserAgent
}
});
}
return secret; return secret;
} }
@@ -610,7 +694,7 @@ const updateSecretHelper = async ({
secretValueTag secretValueTag
}); });
// // (EE) add version for new secret // (EE) add version for new secret
await EESecretService.addSecretVersions({ await EESecretService.addSecretVersions({
secretVersions: [secretVersion] secretVersions: [secretVersion]
}); });
@@ -636,6 +720,24 @@ const updateSecretHelper = async ({
workspaceId workspaceId
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets modified',
distinctId: TelemetryService.getDistinctId({
authData
}),
properties: {
numberOfSecrets: 1,
environment,
workspaceId,
channel: authData.authChannel,
userAgent: authData.authUserAgent
}
});
}
return secret; return secret;
} }
@@ -728,6 +830,24 @@ const deleteSecretHelper = async ({
workspaceId workspaceId
}); });
const postHogClient = TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: 'secrets deleted',
distinctId: TelemetryService.getDistinctId({
authData
}),
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: authData.authChannel,
userAgent: authData.authUserAgent
}
});
}
return ({ return ({
secrets, secrets,
secret secret
@@ -737,6 +857,7 @@ const deleteSecretHelper = async ({
export { export {
validateClientForSecret, validateClientForSecret,
validateClientForSecrets, validateClientForSecrets,
createSecretBlindIndexDataHelper,
generateSecretBlindIndexHelper, generateSecretBlindIndexHelper,
createSecretHelper, createSecretHelper,
getSecretsHelper, getSecretsHelper,
+3 -16
View File
@@ -30,6 +30,7 @@ import {
} from '../variables'; } from '../variables';
import { getEncryptionKey } from '../config'; import { getEncryptionKey } from '../config';
import { encryptSymmetric } from '../utils/crypto'; import { encryptSymmetric } from '../utils/crypto';
import { SecretService } from '../services';
/** /**
* Validate authenticated clients for workspace with id [workspaceId] based * Validate authenticated clients for workspace with id [workspaceId] based
@@ -158,23 +159,9 @@ const createWorkspace = async ({
}); });
// initialize blind index salt for workspace // initialize blind index salt for workspace
const salt = crypto.randomBytes(16).toString('base64'); await SecretService.createSecretBlindIndexData({
workspaceId: workspace._id
const {
ciphertext: encryptedSaltCiphertext,
iv: saltIV,
tag: saltTag
} = encryptSymmetric({
plaintext: salt,
key: getEncryptionKey()
}); });
await new SecretBlindIndexData({
workspace: workspace._id,
encryptedSaltCiphertext,
saltIV,
saltTag
}).save();
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -9,4 +9,5 @@ export interface AuthData {
authPayload: IUser | IServiceAccount | IServiceTokenData; authPayload: IUser | IServiceAccount | IServiceTokenData;
authChannel: string; authChannel: string;
authIP: string; authIP: string;
authUserAgent: string;
} }
+2 -1
View File
@@ -91,7 +91,8 @@ const requireAuth = ({
authMode, authMode,
authPayload, // User, ServiceAccount, ServiceTokenData authPayload, // User, ServiceAccount, ServiceTokenData
authChannel: getChannelFromUserAgent(req.headers['user-agent']), authChannel: getChannelFromUserAgent(req.headers['user-agent']),
authIP: req.ip authIP: req.ip,
authUserAgent: req.headers['user-agent'] ?? 'other'
} }
return next(); return next();
+4 -3
View File
@@ -1,3 +1,4 @@
import { Types } from 'mongoose';
import { import {
getSecretsHelper, getSecretsHelper,
encryptSymmetricHelper, encryptSymmetricHelper,
@@ -21,7 +22,7 @@ class BotService {
workspaceId, workspaceId,
environment environment
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
environment: string; environment: string;
}) { }) {
return await getSecretsHelper({ return await getSecretsHelper({
@@ -41,7 +42,7 @@ class BotService {
workspaceId, workspaceId,
plaintext plaintext
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
plaintext: string; plaintext: string;
}) { }) {
return await encryptSymmetricHelper({ return await encryptSymmetricHelper({
@@ -65,7 +66,7 @@ class BotService {
iv, iv,
tag tag
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
ciphertext: string; ciphertext: string;
iv: string; iv: string;
tag: string; tag: string;
+3 -1
View File
@@ -1,8 +1,10 @@
import { Types } from 'mongoose';
import { handleEventHelper } from '../helpers/event'; import { handleEventHelper } from '../helpers/event';
interface Event { interface Event {
name: string; name: string;
workspaceId: string; workspaceId: Types.ObjectId;
environment?: string;
payload: any; payload: any;
} }
+4 -2
View File
@@ -52,9 +52,11 @@ class IntegrationService {
* @param {Object} obj.workspaceId - id of workspace * @param {Object} obj.workspaceId - id of workspace
*/ */
static async syncIntegrations({ static async syncIntegrations({
workspaceId workspaceId,
environment
}: { }: {
workspaceId: string; workspaceId: Types.ObjectId;
environment?: string;
}) { }) {
return await syncIntegrationsHelper({ return await syncIntegrationsHelper({
workspaceId workspaceId
+29 -86
View File
@@ -11,6 +11,7 @@ import {
DeleteSecretParams DeleteSecretParams
} from '../interfaces/services/SecretService'; } from '../interfaces/services/SecretService';
import { import {
createSecretBlindIndexDataHelper,
generateSecretBlindIndexHelper, generateSecretBlindIndexHelper,
createSecretHelper, createSecretHelper,
getSecretsHelper, getSecretsHelper,
@@ -20,12 +21,28 @@ import {
} from '../helpers/secrets'; } from '../helpers/secrets';
class SecretService { class SecretService {
/**
* Create secret blind index data containing encrypted blind index salt
* for workspace with id [workspaceId]
* @param {Object} obj
* @param {Types.ObjectId} obj.workspaceId
*/
static async createSecretBlindIndexData({
workspaceId
}: {
workspaceId: Types.ObjectId;
}) {
return await createSecretBlindIndexDataHelper({
workspaceId
});
}
/** /**
* Create and return blind index for secret with * Create and return blind index for secret with
* name [secretName] part of workspace with id [workspaceId] * name [secretName] part of workspace with id [workspaceId]
* @param {Object} obj * @param {Object} obj
* @param {Object} obj.secretName - name of secret to generate blind index for * @param {String} obj.secretName - name of secret to generate blind index for
* @param {Object} obj.workspaceId - id of workspace that secret belongs to * @param {Types.ObjectId} obj.workspaceId - id of workspace that secret belongs to
*/ */
static async generateSecretBlindIndex({ static async generateSecretBlindIndex({
secretName, secretName,
@@ -50,32 +67,8 @@ class SecretService {
* @param {AuthData} obj.authData - authentication data on request * @param {AuthData} obj.authData - authentication data on request
* @returns * @returns
*/ */
static async createSecret({ static async createSecret(createSecretParams: CreateSecretParams) {
secretName, return await createSecretHelper(createSecretParams);
workspaceId,
environment,
type,
authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}: CreateSecretParams) {
return await createSecretHelper({
secretName,
workspaceId,
environment,
type,
authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
} }
/** /**
@@ -86,16 +79,8 @@ class SecretService {
* @param {AuthData} obj.authData - authentication data on request * @param {AuthData} obj.authData - authentication data on request
* @returns * @returns
*/ */
static async getSecrets({ static async getSecrets(getSecretsParams: GetSecretsParams) {
workspaceId, return await getSecretsHelper(getSecretsParams);
environment,
authData
}: GetSecretsParams) {
return await getSecretsHelper({
workspaceId,
environment,
authData
});
} }
/** /**
@@ -108,20 +93,8 @@ class SecretService {
* @param {AuthData} obj.authData - authentication data on request * @param {AuthData} obj.authData - authentication data on request
* @returns * @returns
*/ */
static async getSecret({ static async getSecret(getSecretParams: GetSecretParams) {
secretName, return await getSecretHelper(getSecretParams);
workspaceId,
environment,
type,
authData
}: GetSecretParams) {
return await getSecretHelper({
secretName,
workspaceId,
environment,
type,
authData
});
} }
/** /**
@@ -137,26 +110,8 @@ class SecretService {
* @param {AuthData} obj.authData - authentication data on request * @param {AuthData} obj.authData - authentication data on request
* @returns * @returns
*/ */
static async updateSecret({ static async updateSecret(updateSecretParams: UpdateSecretParams) {
secretName, return await updateSecretHelper(updateSecretParams);
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag,
authData
}: UpdateSecretParams) {
return await updateSecretHelper({
secretName,
workspaceId,
environment,
type,
authData,
secretValueCiphertext,
secretValueIV,
secretValueTag
});
} }
/** /**
@@ -169,20 +124,8 @@ class SecretService {
* @param {AuthData} obj.authData - authentication data on request * @param {AuthData} obj.authData - authentication data on request
* @returns * @returns
*/ */
static async deleteSecret({ static async deleteSecret(deleteSecretParams: DeleteSecretParams) {
secretName, return await deleteSecretHelper(deleteSecretParams);
workspaceId,
environment,
type,
authData
}: DeleteSecretParams) {
return await deleteSecretHelper({
secretName,
workspaceId,
environment,
type,
authData
});
} }
} }
+21 -34
View File
@@ -1,5 +1,6 @@
import { PostHog } from 'posthog-node'; import { PostHog } from 'posthog-node';
import { getLogger } from '../utils/logger'; import { getLogger } from '../utils/logger';
import { AuthData } from '../interfaces/middleware';
import { import {
getNodeEnv, getNodeEnv,
getTelemetryEnabled, getTelemetryEnabled,
@@ -11,7 +12,8 @@ import {
User, User,
IServiceAccount, IServiceAccount,
ServiceAccount, ServiceAccount,
IServiceTokenData IServiceTokenData,
ServiceTokenData
} from '../models'; } from '../models';
import { import {
BadRequestError BadRequestError
@@ -48,42 +50,27 @@ class Telemetry {
return postHogClient; return postHogClient;
} }
/** static getDistinctId ({
* Return a distinct id for client to be used for logging telemetry authData
*/
static getDistinctId = ({
user,
serviceAccount,
serviceTokenData
}: { }: {
user?: IUser; authData: AuthData;
serviceAccount?: IServiceAccount; }) {
serviceTokenData?: any; // TODO: fix (it's ServiceTokenData with user populated) let distinctId: any = '';
}) => { if (authData.authPayload instanceof User) {
distinctId = authData.authPayload.email;
// TODO: modify to accept authData instead } else if (authData.authPayload instanceof ServiceAccount) {
distinctId = `sa.${authData.authPayload._id.toString()}`;
let distinctId = ''; } else if (authData.authPayload instanceof ServiceTokenData) {
if (authData.authPayload.user instanceof User) {
if (user) { distinctId = authData.authPayload?.user.email;
distinctId = user.email; } else if (authData.authPayload?.serviceAccount) {
distinctId = distinctId = `sa.${authData.authPayload.serviceAccount.toString()}`;
}
} }
if (serviceAccount) { if (distinctId === '') throw BadRequestError({
distinctId = `sa.${serviceAccount._id.toString()}`; message: 'Failed to obtain distinct id for logging telemetry'
} });
if (serviceTokenData?.user && serviceTokenData?.user instanceof User) {
distinctId = serviceTokenData.user.email;
} else if (serviceTokenData?.serviceAccount && serviceTokenData?.serviceAccount instanceof ServiceAccount) {
distinctId = `sa.${serviceTokenData.serviceAccount._id.toString()}`;
}
if (distinctId === '') {
throw BadRequestError({
message: 'Failed to obtain distinct id for logging telemetry'
});
}
return distinctId; return distinctId;
} }