Skip to content

Commit

Permalink
Finish v1 audit logs, secret versioning, version all unversioned secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
dangtony98 committed Jan 2, 2023
1 parent 4576e8f commit a8f0c39
Show file tree
Hide file tree
Showing 19 changed files with 401 additions and 195 deletions.
4 changes: 3 additions & 1 deletion backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { apiLimiter } from './helpers/rateLimiter';

import {
workspace as eeWorkspaceRouter,
secret as eeSecretRouter
secret as eeSecretRouter,
action as eeActionRouter
} from './ee/routes/v1';
import {
signup as v1SignupRouter,
Expand Down Expand Up @@ -69,6 +70,7 @@ if (NODE_ENV === 'production') {
// (EE) routes
app.use('/api/v1/secret', eeSecretRouter);
app.use('/api/v1/workspace', eeWorkspaceRouter);
app.use('/api/v1/action', eeActionRouter);

// v1 routes
app.use('/api/v1/signup', v1SignupRouter);
Expand Down
2 changes: 0 additions & 2 deletions backend/src/controllers/v2/workspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,6 @@ export const getWorkspaceServiceTokens = async (
*/
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId]

try {
let { secrets }: { secrets: V2PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
Expand Down Expand Up @@ -400,7 +399,6 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
keys
});


if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
Expand Down
27 changes: 19 additions & 8 deletions backend/src/ee/controllers/v1/actionController.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Action } from '../../models';
import { Action, SecretVersion } from '../../models';
import { ActionNotFoundError } from '../../../utils/errors';

export const getAction = (req: Request, res: Response) => {
export const getAction = async (req: Request, res: Response) => {
let action;
// try {
// const { actionId } = req.params;
try {
const { actionId } = req.params;

// action = await Action.findById(actionId);
action = await Action
.findById(actionId)
.populate([
'payload.secretVersions.oldSecretVersion',
'payload.secretVersions.newSecretVersion'
]);


// } catch (err) {
if (!action) throw ActionNotFoundError({
message: 'Failed to find action'
});

// }
} catch (err) {
throw ActionNotFoundError({
message: 'Failed to find action'
});
}

return res.status(200).send({
action
Expand Down
17 changes: 13 additions & 4 deletions backend/src/ee/controllers/v1/workspaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,23 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
let logs
try {
const { workspaceId } = req.params;
const { userId, actionNames } = req.query;

const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
const filters: any = req.query.filters || {};

filters.workspace = workspaceId;

logs = await Log.find(filters)
logs = await Log.find({
workspace: workspaceId,
...( userId ? { user: userId } : {}),
...(
actionNames
? {
actionNames: {
$in: actionNames
}
} : {}
)
})
.skip(offset)
.limit(limit)
.populate('actions')
Expand Down
112 changes: 112 additions & 0 deletions backend/src/ee/helpers/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Secret } from '../../models';
import { SecretVersion, Action } from '../models';
import { ACTION_UPDATE_SECRETS } from '../../variables';

/**
* Create an (audit) action for secrets including
* add, delete, update, and read actions.
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {ObjectId[]} obj.secretIds - ids of relevant secrets
* @returns {Action} action - new action
*/
const createActionSecretHelper = async ({
name,
userId,
workspaceId,
secretIds
}: {
name: string;
userId: string;
workspaceId: string;
secretIds: Types.ObjectId[];
}) => {

let action;
let latestSecretVersions;
try {
if (name === ACTION_UPDATE_SECRETS) {
// case: action is updating secrets
// -> add old and new secret versions

// TODO: make query more efficient
latestSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
},
},
},
{
$sort: { version: -1 },
},
{
$group: {
_id: "$secret",
versions: { $push: "$$ROOT" },
},
},
{
$project: {
_id: 0,
secret: "$_id",
versions: { $slice: ["$versions", 2] },
},
}
]))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id
}));


} else {
// case: action is adding, deleting, or reading secrets
// -> add new secret versions
latestSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
}
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // secret version id
}
},
{
$sort: { version: -1 }
}
])
.exec())
.map((s) => ({
newSecretVersion: s.versionId
}));
}

action = await new Action({
name,
user: userId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();

} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action');
}

return action;
}

export { createActionSecretHelper };
1 change: 1 addition & 0 deletions backend/src/ee/helpers/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const createLogHelper = async ({
log = await new Log({
user: userId,
workspace: workspaceId,
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress
Expand Down
29 changes: 27 additions & 2 deletions backend/src/ee/helpers/secret.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Secret
Expand Down Expand Up @@ -59,16 +60,40 @@ const addSecretVersionsHelper = async ({
}: {
secretVersions: ISecretVersion[]
}) => {
let newSecretVersions;
try {
await SecretVersion.insertMany(secretVersions);
newSecretVersions = await SecretVersion.insertMany(secretVersions);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to add secret versions');
}

return newSecretVersions;
}

const markDeletedSecretVersionsHelper = async ({
secretIds
}: {
secretIds: Types.ObjectId[];
}) => {
try {
await SecretVersion.updateMany({
secret: { $in: secretIds }
}, {
isDeleted: true
}, {
new: true
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to mark secret versions as deleted');
}
}

export {
takeSecretSnapshotHelper,
addSecretVersionsHelper
addSecretVersionsHelper,
markDeletedSecretVersionsHelper
}
10 changes: 8 additions & 2 deletions backend/src/ee/models/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ const actionSchema = new Schema<IAction>(
},
payload: {
secretVersions: [{
type: Schema.Types.ObjectId,
ref: 'SecretVersion'
oldSecretVersion: {
type: Schema.Types.ObjectId,
ref: 'SecretVersion'
},
newSecretVersion: {
type: Schema.Types.ObjectId,
ref: 'SecretVersion'
}
}]
}
}, {
Expand Down
20 changes: 19 additions & 1 deletion backend/src/ee/models/log.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Schema, model, Types } from 'mongoose';
import {
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS
} from '../../variables';

export interface ILog {
_id: Types.ObjectId;
user?: Types.ObjectId;
workspace?: Types.ObjectId;
actionNames: string[];
actions: Types.ObjectId[];
channel: string;
ipAddress?: string;
Expand All @@ -19,9 +26,20 @@ const logSchema = new Schema<ILog>(
type: Schema.Types.ObjectId,
ref: 'Workspace'
},
actionNames: {
type: [String],
enum: [
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS
],
required: true
},
actions: [{
type: Schema.Types.ObjectId,
ref: 'Action'
ref: 'Action',
required: true
}],
channel: {
type: String,
Expand Down
1 change: 0 additions & 1 deletion backend/src/ee/routes/v1/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ router.get(
param('workspaceId').exists().trim(),
query('offset').exists().isInt(),
query('limit').exists().isInt(),
query('filters').exists(),
validateRequest,
workspaceController.getWorkspaceLogs
);
Expand Down
Loading

0 comments on commit a8f0c39

Please sign in to comment.