Skip to content

Commit

Permalink
Modify secret snapshots to point to secret versions
Browse files Browse the repository at this point in the history
  • Loading branch information
dangtony98 committed Jan 3, 2023
1 parent 9d0e269 commit c7c5a94
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 149 deletions.
2 changes: 2 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { apiLimiter } from './helpers/rateLimiter';
import {
workspace as eeWorkspaceRouter,
secret as eeSecretRouter,
secretSnapshot as eeSecretSnapshotRouter,
action as eeActionRouter
} from './ee/routes/v1';
import {
Expand Down Expand Up @@ -69,6 +70,7 @@ if (NODE_ENV === 'production') {

// (EE) routes
app.use('/api/v1/secret', eeSecretRouter);
app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter);
app.use('/api/v1/workspace', eeWorkspaceRouter);
app.use('/api/v1/action', eeActionRouter);

Expand Down
2 changes: 2 additions & 0 deletions backend/src/ee/controllers/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as stripeController from './stripeController';
import * as secretController from './secretController';
import * as secretSnapshotController from './secretSnapshotController';
import * as workspaceController from './workspaceController';
import * as actionController from './actionController';

export {
stripeController,
secretController,
secretSnapshotController,
workspaceController,
actionController
}
27 changes: 27 additions & 0 deletions backend/src/ee/controllers/v1/secretSnapshotController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { SecretSnapshot } from '../../models';

export const getSecretSnapshot = async (req: Request, res: Response) => {
let secretSnapshot;
try {
const { secretSnapshotId } = req.params;

secretSnapshot = await SecretSnapshot
.findById(secretSnapshotId)
.populate('secretVersions');

if (!secretSnapshot) throw new Error('Failed to find secret snapshot');

} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get secret snapshot'
});
}

return res.status(200).send({
secretSnapshot
});
}
44 changes: 27 additions & 17 deletions backend/src/ee/helpers/secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,44 @@ import {
}: {
workspaceId: string;
}) => {

let secretSnapshot;
try {
const secrets = await Secret.find({
const secretIds = (await Secret.find({
workspace: workspaceId
});
}, '_id')).map((s) => s._id);

const 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) => s.versionId);

const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId
}).sort({ version: -1 });

if (!latestSecretSnapshot) {
// case: no snapshots exist for workspace -> create first snapshot
await new SecretSnapshot({
workspace: workspaceId,
version: 1,
secrets
}).save();

return;
}

// case: snapshots exist for workspace
secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
version: latestSecretSnapshot.version + 1,
secrets
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions
}).save();

} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
Expand Down
7 changes: 7 additions & 0 deletions backend/src/ee/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import requireLicenseAuth from './requireLicenseAuth';
import requireSecretSnapshotAuth from './requireSecretSnapshotAuth';

export {
requireLicenseAuth,
requireSecretSnapshotAuth
}
50 changes: 50 additions & 0 deletions backend/src/ee/middleware/requireSecretSnapshotAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Request, Response, NextFunction } from 'express';
import { UnauthorizedRequestError, SecretSnapshotNotFoundError } from '../../utils/errors';
import { SecretSnapshot } from '../models';
import {
validateMembership
} from '../../helpers/membership';

/**
* Validate if user on request has proper membership for secret snapshot
* @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.acceptedStatuses - accepted workspace statuses
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
*/
const requireSecretSnapshotAuth = ({
acceptedRoles,
acceptedStatuses
}: {
acceptedRoles: string[];
acceptedStatuses: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { secretSnapshotId } = req.params;

const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);

if (!secretSnapshot) {
return next(SecretSnapshotNotFoundError({
message: 'Failed to find secret snapshot'
}));
}

await validateMembership({
userId: req.user._id.toString(),
workspaceId: secretSnapshot.workspace.toString(),
acceptedRoles,
acceptedStatuses
});

req.secretSnapshot = secretSnapshot as any;

next();
} catch (err) {
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret snapshot' }));
}
}
}

export default requireSecretSnapshotAuth;
86 changes: 5 additions & 81 deletions backend/src/ee/models/secretSnapshot.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
import { Schema, model, Types } from 'mongoose';
import {
SECRET_SHARED,
SECRET_PERSONAL,
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD
} from '../../variables';

export interface ISecretSnapshot {
workspace: Types.ObjectId;
version: number;
secrets: {
version: number;
workspace: Types.ObjectId;
type: string;
user: Types.ObjectId;
environment: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretKeyHash: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
secretValueHash: string;
}[]
secretVersions: Types.ObjectId[];
}

const secretSnapshotSchema = new Schema<ISecretSnapshot>(
Expand All @@ -39,64 +17,10 @@ const secretSnapshotSchema = new Schema<ISecretSnapshot>(
type: Number,
required: true
},
secrets: [{
version: {
type: Number,
default: 1,
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: 'User'
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
secretKeyCiphertext: {
type: String,
required: true
},
secretKeyIV: {
type: String, // symmetric
required: true
},
secretKeyTag: {
type: String, // symmetric
required: true
},
secretKeyHash: {
type: String,
required: true
},
secretValueCiphertext: {
type: String,
required: true
},
secretValueIV: {
type: String, // symmetric
required: true
},
secretValueTag: {
type: String, // symmetric
required: true
},
secretValueHash: {
type: String,
required: true
}
secretVersions: [{
type: Schema.Types.ObjectId,
ref: 'SecretVersion',
required: true
}]
},
{
Expand Down
41 changes: 41 additions & 0 deletions backend/src/ee/models/secretVersion.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
import { Schema, model, Types } from 'mongoose';
import {
SECRET_SHARED,
SECRET_PERSONAL,
ENV_DEV,
ENV_TESTING,
ENV_STAGING,
ENV_PROD
} from '../../variables';

/**
* TODO:
* 1. Modify SecretVersion to also contain XX
* - type
* - user
* - environment
* 2. Modify SecretSnapshot to point to arrays of SecretVersion
*/

export interface ISecretVersion {
_id?: Types.ObjectId;
secret: Types.ObjectId;
version: number;
workspace: Types.ObjectId; // new
type: string; // new
user: Types.ObjectId; // new
environment: string; // new
isDeleted: boolean;
secretKeyCiphertext: string;
secretKeyIV: string;
Expand All @@ -27,6 +48,26 @@ const secretVersionSchema = new Schema<ISecretVersion>(
default: 1,
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: 'User'
},
environment: {
type: String,
enum: [ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD],
required: true
},
isDeleted: {
type: Boolean,
default: false,
Expand Down
2 changes: 2 additions & 0 deletions backend/src/ee/routes/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import secret from './secret';
import secretSnapshot from './secretSnapshot';
import workspace from './workspace';
import action from './action';

export {
secret,
secretSnapshot,
workspace,
action
}
2 changes: 1 addition & 1 deletion backend/src/ee/routes/v1/secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
requireSecretAuth,
validateRequest
} from '../../../middleware';
import { body, query, param } from 'express-validator';
import { query, param } from 'express-validator';
import { secretController } from '../../controllers/v1';
import { ADMIN, MEMBER, COMPLETED, GRANTED } from '../../../variables';

Expand Down
26 changes: 26 additions & 0 deletions backend/src/ee/routes/v1/secretSnapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import express from 'express';
const router = express.Router();
import {
requireSecretSnapshotAuth
} from '../../middleware';
import {
requireAuth,
validateRequest
} from '../../../middleware';
import { param } from 'express-validator';
import { ADMIN, MEMBER, GRANTED } from '../../../variables';
import { secretSnapshotController } from '../../controllers/v1';

router.get(
'/:secretSnapshotId',
requireAuth,
requireSecretSnapshotAuth({
acceptedRoles: [ADMIN, MEMBER],
acceptedStatuses: [GRANTED]
}),
param('secretSnapshotId').exists().trim(),
validateRequest,
secretSnapshotController.getSecretSnapshot
);

export default router;
Loading

0 comments on commit c7c5a94

Please sign in to comment.